题目描述
小小甜心的基地的周围出现了 n n n 个新的矿脉,矿脉 i i i 拥有的矿石数量为 a i a_i ai 。基地、矿脉之间存在 m m m 条无向边连通,从 u u u 与 v v v 之间移动一次需要 w w w 的能量。现在你可以指挥基地(下标为 0 0 0)中的 x x x 名矿工前往不同的矿脉采集矿石,但每名矿工只能采集一处矿脉的矿石,每处矿脉的矿石也只能被采集一次。此外,你只能支付所有矿工前往矿脉总计 y y y 的能量(矿工返回基地不需要能量),请计算你最多可以收集的矿石数。
输入描述
第一行,四个整数 n n n, m m m, x x x, y y y ( 1 ≤ n ≤ 100 1\leq n\leq 100 1≤n≤100, 1 ≤ m ≤ n ( n − 1 ) / 2 1\leq m\leq n(n - 1)/2 1≤m≤n(n−1)/2, 0 ≤ x ≤ 50 0\leq x\leq 50 0≤x≤50, 0 ≤ y ≤ 10000 0\leq y\leq 10000 0≤y≤10000) 表示 n n n 个矿脉 m m m 条无向边, x x x 名矿工, y y y 点能量;
第二行, n n n 个整数 a i ( 0 ≤ a i ≤ 1000 ) a_i(0\leq a_i\leq 1000) ai(0≤ai≤1000) 表示对应矿脉所拥有的矿石数量;
接下来
m
m
m 行,每行三个整数
u
u
u,
v
v
v,
w
w
w
(
0
≤
u
,
v
≤
n
,
1
≤
w
≤
1000
)
(0\leq u,v\leq n, 1\leq w\leq 1000)
(0≤u,v≤n,1≤w≤1000) 表示
u
u
u 与
v
v
v 之间存在一条需要消耗
w
w
w 能量的无向边。
(题目保证输入无重边、无自环。)
输出描述
一个整数,表示你所能收集的最大矿石数。
样例输入
3 5 2 15
7 7 7
0 1 5
0 2 5
1 2 5
1 3 5
2 3 5
样例输出
14
题解
Dijkstra最短路 + 二维背包DP。
基地和矿脉构成了一个无向图,容易想到使用最短路算法求出基地到每个矿脉所需的最少能量。
然后问题就变成了一个比较明显的背包DP,题目里有两个限制条件:
①
①
① 支付的能量为
y
y
y;
②
②
② 矿工人数不超过
x
x
x,我们可以把它们作为DP状态中的两个维度,为了节省空间,代码实现中使用了二维滚动数组。(这里略去对状态转移方程的说明)
最终,算法的时间复杂度为
O
(
n
2
+
n
x
y
)
O(n^2 + nxy)
O(n2+nxy), 此处求最短路使用的是
O
(
n
2
)
O(n^2)
O(n2) 时间复杂度的Dijkstra算法。
实现的参考代码如下:
#include <cstdio>
#include <cstring>
#include <algorithm>
const int N = 105, INF = 0x3f3f3f3f;
int n, m, x, y, dis[N], vis[N], G[N][N], price[N];
int f[10005][51];
void dijkstra() {
dis[0] = 0;
vis[0] = 1;
int s = 0, t, dmin;
for (int i = 0; i <= n; ++i) {
dmin = 0x3f3f3f3f + 1;
for (int j = 0; j <= n; ++j)
if (!vis[j] && dis[j] > dis[s] + G[s][j])
dis[j] = dis[s] + G[s][j];
for (int j = 0; j <= n; ++j)
if (!vis[j] && dis[j] < dmin) {
dmin = dis[j];
t = j;
}
vis[t] = 1;
s = t;
}
}
int main() {
memset(dis, 0x3f, sizeof dis);
memset(G, 0x3f, sizeof G);
scanf("%d%d%d%d", &n, &m, &x, &y);
G[0][0] = 0;
for (int i = 1; i <= n; ++i) {
G[i][i] = 0;
scanf("%d", price + i);
}
for (int i = 0, u, v, w; i < m; ++i) {
scanf("%d%d%d", &u, &v, &w);
G[u][v] = G[v][u] = w;
}
dijkstra();
for (int i = 1; i <= n; ++i) {
if (dis[i] == INF) continue;
for (int j = y; j >= dis[i]; --j)
for (int k = x; k > 0; --k)
f[j][k] = std::max(f[j][k], f[j - dis[i]][k - 1] + price[i]);
}
printf("%d\n", f[y][x]);
return 0;
}
总结
作为在LevOJ上AC的第400道题目,这道题目还是很有纪念意义的。这题也算是进一步帮助自己巩固了背包DP,还是有些难度的(大佬请忽略这句话),中途还在担心这个时间复杂度( 100 × 10000 × 50 100\times 10000\times 50 100×10000×50)会不会超时,实测结果还是挺稳的。