LevOJ P1761 采矿

题目描述

小小甜心的基地的周围出现了 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 1n100, 1 ≤ m ≤ n ( n − 1 ) / 2 1\leq m\leq n(n - 1)/2 1mn(n1)/2, 0 ≤ x ≤ 50 0\leq x\leq 50 0x50, 0 ≤ y ≤ 10000 0\leq y\leq 10000 0y10000) 表示 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(0ai1000) 表示对应矿脉所拥有的矿石数量;

接下来 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) (0u,vn,1w1000) 表示 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)会不会超时,实测结果还是挺稳的。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值