暑假N天乐 —— 完全背包及变形

好吧先承认一下错误...昨天放假然后牛客补题就咕咕咕了。

云顶之弈真好玩 六刺客赛高

掐指一算离牛客暑假多校开始就剩两天了...哦豁完蛋。

目前来说明天的计划是多重背包+分组背包,也算把背包先画上句号【树形背包打算后面和树形DP一起搞】。然后明天应该会把这几天背包的总结贴上,今天就emmm只上题解好了。

[UVA-674 Coin Change] 完全背包裸题

https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=615

给定一个数要拆成由【50、25、10、5、1】组成,每种都无限,问有几种拆法。

就大概是个纯板子,就放这了。

#include <map>
#include <set>
#include <list>
#include <cmath>
#include <ctime>
#include <deque>
#include <stack>
#include <queue>
#include <bitset>
#include <cctype>
#include <cstdio>
#include <vector>
#include <string>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iomanip>
#include <numeric>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const double PI = acos(-1.0);
const double eps = 1e-6;
const int inf = 0x3f3f3f3f;
const int mod = 1e9 + 7;

int val[5];
ll dp[10005];

int main() {
	val[0] = 1;
	val[1] = 5;
	val[2] = 10;
	val[3] = 25;
	val[4] = 50;
	int n;
	while(~scanf("%d", &n)) {
		memset(dp, 0, sizeof(dp));
		dp[0] = 1;
		for(int i = 0; i < 5; i++) {
			for(int j = val[i]; j <= n; j++) {
				dp[j] += dp[j-val[i]];
			}
		}
		printf("%lld
", dp[n]);
	}
	return 0;
}

[POJ- Dollars] 完全背包裸题

https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=83

和上题基本意思一样,但因为小数的存在,所以需要做精度处理,只能说多了个坑(然后我就跳进去了)。

#include <map>
#include <set>
#include <list>
#include <cmath>
#include <ctime>
#include <deque>
#include <stack>
#include <queue>
#include <bitset>
#include <cctype>
#include <cstdio>
#include <vector>
#include <string>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iomanip>
#include <numeric>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const double PI = acos(-1.0);
const double eps = 1e-6;
const int inf = 0x3f3f3f3f;
const int mod = 1e9 + 7;

int val[15];
ll dp[50005];

int main() {
	val[0] = 5;
	val[1] = 10;
	val[2] = 20;
	val[3] = 50;
	val[4] = 100;
	val[5] = 200;
	val[6] = 500;
	val[7] = 1000;
	val[8] = 2000;
	val[9] = 5000;
	val[10] = 10000;
	int n;
	int a, b;
	while(scanf("%d.%d",&a,&b)&&(a+b)) {
		n = 100*a + b;
		memset(dp, 0, sizeof(dp));
		dp[0] = 1;
		for(int i = 0; i < 11; i++) {
			for(int j = val[i]; j <= n; j++) {
				dp[j] += dp[j-val[i]];
			}
		}
		printf("%6.2f%17lld
", n*1.0/100, dp[n]);
	}
	return 0;
}

[POJ-3181 Dollar Dayz] 完全背包常规(整数划分高精度处理)

http://poj.org/problem?id=3181

上一题的更进一步,把精度问题从小数点换成了爆longlong。

然后这题其实是正统的整数划分问题。以下详解转自网络(侵删):

整数划分是把一个正整数 N 拆分成一组数相加并且等于 N 的问题。

假设(F(N,M))表示整数 N 的划分个数,其中 M 表示将 N 拆分后的序列中最大数。

考虑边界状态:

  1. M = 1 或者 N = 1 只有一个划分,即:(F(1,1) = 1)
  2. M = N:等于把M - 1 的划分数加 1 即:(F(N,N) = F(N,N-1) + 1)
  3. M > N:按理说,N 划分后的序列中最大数是不会超过 N 的,所以(F(N,M ) = F(N,N))
  4. M < N:这个是最常见的,他应该是序列中最大数为 M-1 的划分和 N-M 的划分之和,所以(F(N,M) = F(N, M-1) + F(N-M,M))

用动态规划来表示【(dp[n][m])表示整数 n 的划分中,每个数不大于 m 的划分数】:$$dp[n][m]= dp[n][m-1]+ dp[n-m][m]$$

#include <map>
#include <set>
#include <list>
#include <cmath>
#include <ctime>
#include <deque>
#include <stack>
#include <queue>
#include <bitset>
#include <cctype>
#include <cstdio>
#include <vector>
#include <string>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iomanip>
#include <numeric>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const double PI = acos(-1.0);
const double eps = 1e-6;
const int inf = 0x3f3f3f3f;
const int mod = 1e9 + 7;

const int maxn = 1e3+5;

ll a[maxn][maxn], b[maxn][maxn];

int main() {
	int n, k;
	ll MAX = 1;
	for(int i = 0; i < 18; i++) {
		MAX *= 10;
	}
	while(~scanf("%d%d", &n, &k)) {
		memset(a, 0, sizeof(a));
		memset(b, 0, sizeof(b));
		for(int i = 0; i <= k; i++) {
			a[0][i] = 1;
		}
		for(int j = 1; j <= k; j++) {
			for(int i = 1; i <= n; i++) {
				if(i < j) {
					a[i][j] = a[i][j-1];
					b[i][j] = b[i][j-1];
				}
				else {
					b[i][j] = b[i-j][j] + b[i][j-1] + (a[i-j][j]+a[i][j-1])/MAX;
					a[i][j] = (a[i-j][j]+a[i][j-1]) % MAX;
				}
			}
		}
		if(b[n][k]) {
			printf("%lld", b[n][k]);
		}
		printf("%lld
", a[n][k]);
	}
	return 0;
}

[POJ-1787 Charlie's Change] 完全背包常规

http://poj.org/problem?id=1787

买咖啡,有4种硬币,要求付款金额最小的情况下花最多的硬币。

多重背包可做,但完全背包更快且好写,缺点...难想。

#include <map>
#include <set>
#include <list>
#include <cmath>
#include <ctime>
#include <deque>
#include <stack>
#include <queue>
#include <bitset>
#include <cctype>
#include <cstdio>
#include <vector>
#include <string>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iomanip>
#include <numeric>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const double PI = acos(-1.0);
const double eps = 1e-6;
const int inf = 0x3f3f3f3f;
const int mod = 1e9 + 7;

const int maxn = 1e5+5;

int val[5];
int dp[maxn];
int vis[maxn];
int ans[maxn];
int temp[maxn];

int main() {
	val[0] = 1;
	val[1] = 5;
	val[2] = 10;
	val[3] = 25;
	int n, a[5];
	while(~scanf("%d%d%d%d%d", &n, &a[0], &a[1], &a[2], &a[3])) {
		if(n == 0 && a[0] == 0 && a[1] == 0 && a[2] == 0 && a[3] == 0) {
			break;
		}
		memset(dp, 0, sizeof(dp));
		memset(temp, 0, sizeof(temp));
		memset(ans, 0, sizeof(ans));
		dp[0] = 1;
		for(int i = 0; i < 4; i++) {
			memset(vis, 0, sizeof(vis));
			for(int j = val[i]; j <= n; j++) {
				if(dp[j-val[i]] && dp[j] < dp[j-val[i]] + 1 && vis[j-val[i]] < a[i]) {
					dp[j] = dp[j-val[i]] + 1;
					vis[j] = vis[j-val[i]] + 1;
					temp[j] = j - val[i];
				}
			}
		}
		if(dp[n] == 0) {
			printf("Charlie cannot buy coffee.
");
		}
		else {
			while(n) {
				ans[n-temp[n]] ++;
				n = temp[n];
			}
			printf("Throw in %d cents, %d nickels, %d dimes, and %d quarters.
", ans[1], ans[5], ans[10], ans[25]);
		}
	}
	return 0;
}

[POJ-3620 The Fewest Coins] 完全背包+多重背包

http://poj.org/problem?id=3260

完全背包和多重背包的综合题...给定 n 种硬币和每种的价值、数量。要买价值为 T 的物品,问经手的硬币最少多少枚。

找零用完全背包,付款用多重背包。先更新找零,最后循环找最小。

#include <map>
#include <set>
#include <list>
#include <cmath>
#include <ctime>
#include <deque>
#include <stack>
#include <queue>
#include <bitset>
#include <cctype>
#include <cstdio>
#include <vector>
#include <string>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iomanip>
#include <numeric>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const double PI = acos(-1.0);
const double eps = 1e-6;
const int inf = 0x3f3f3f3f;
const int mod = 1e9 + 7;

const int maxn = 2e5+5;

int dp1[maxn];	// 付款
int dp2[maxn];	// 找零
int val[105];
int c[105];

int main() {
	int n, t;
	while(~scanf("%d%d", &n, &t)) {
		int temp = 0;
		for(int i = 1; i <= n; i++) {
			scanf("%d", &val[i]);
			temp = max(temp, val[i]);
		}
		for(int i = 1; i <= n; i++) {
			scanf("%d", &c[i]);
		}
		memset(dp1, inf, sizeof(dp1));
		memset(dp2, inf, sizeof(dp2));
		dp1[0] = dp2[0] = 0;
		int MAX = temp*temp + t + 1;
		for(int i = 1; i <= n; i++) {
			int vv = val[i];
			int cc = c[i];
			// 找零(完全背包)
			for(int j = vv; j <= MAX; j++) {	
				dp2[j] = min(dp2[j], dp2[j-vv]+1);
			}
			// 付款(多重背包)
			if(vv*cc >= MAX) {	
				for(int j = vv; j <= MAX; j++) {
					dp1[j] = min(dp1[j], dp1[j-vv]+1);
				}
			}
			else {
				int k = 1;
				while(k < cc) {
					for(int j = MAX; j >= k*vv; j--) {
						dp1[j] = min(dp1[j], dp1[j-k*vv]+k);
					}
					cc -= k;
					k = k*2;
				}
				for(int j = MAX; j >= cc*vv; j--) {
					dp1[j] = min(dp1[j], dp1[j-cc*vv]+cc);
				}
			}
		}
		int ans = inf;
		for(int i = t; i <= MAX; i++) {
			ans = min(ans, dp1[i] + dp2[i-t]);
		}
		if(ans == inf) {
			printf("-1
");
		}
		else {
			printf("%d
", ans);
		}
	}
	return 0;
}

[POJ-2063 Investment] 完全背包常规

http://poj.org/problem?id=2063

算利息...每年的钱可以利滚利问最多拿到多少钱。题目给了一个非常重要的信息...债券的价值是1000的倍数,循环的时候需要先 /1000 进行优化,不然...TLE。

#include <map>
#include <set>
#include <list>
#include <cmath>
#include <ctime>
#include <deque>
#include <stack>
#include <queue>
#include <bitset>
#include <cctype>
#include <cstdio>
#include <vector>
#include <string>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iomanip>
#include <numeric>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const double PI = acos(-1.0);
const double eps = 1e-6;
const int inf = 0x3f3f3f3f;
const int mod = 1e9 + 7;

const int maxn = 1e5+5;

int dp[maxn];
int val[15], b[15];

int main() {
	int t;
	scanf("%d", &t);
	while(t--) {
		int n, y;
		scanf("%d%d", &n, &y);
		int d;
		scanf("%d", &d);
		for(int i = 1; i <= d; i++) {
			scanf("%d%d", &val[i], &b[i]);
		}
		for(int k = 1; k <= y; k++) {
			memset(dp, 0, sizeof(dp));
			for(int i = 1; i <= d; i++) {
				for(int j = val[i]/1000; j <= n/1000; j++) {
					dp[j] = max(dp[j], dp[j-val[i]/1000]+b[i]);
				}
			}
			n += dp[n/1000];
		}
		printf("%d
", n);
	}
	return 0;
}

[ZOJ-3623 Battle Ships] 完全背包变形

http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=3623

建造战机打怪,每个战机有建造时间和攻击力(建好了就能一直打),问最快多久打死boss。

用时间作为 dp 数组的下标、记录当前总伤害。最后再循环找最快时间。

#include <map>
#include <set>
#include <list>
#include <cmath>
#include <ctime>
#include <deque>
#include <stack>
#include <queue>
#include <bitset>
#include <cctype>
#include <cstdio>
#include <vector>
#include <string>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iomanip>
#include <numeric>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const double PI = acos(-1.0);
const double eps = 1e-6;
const int inf = 0x3f3f3f3f;
const int mod = 1e9 + 7;

const int maxn = 1e3+5;

int t[35], d[35];
int dp[maxn];

int main() {
	int n, l;
	while(~scanf("%d%d", &n, &l)) {
		for(int i = 1; i <= n; i++) {
			scanf("%d%d", &t[i], &d[i]);
		}
		memset(dp, 0, sizeof(dp));
		for(int i = 1; i <= n; i++) {
			for(int j = 0; j < 400; j++) {
				dp[j+t[i]] = max(dp[j+t[i]], dp[j]+d[i]*j);
			}
		}
		int ans = 0;
		for(int i = 0; i < 400; i++) {
			if(dp[i] >= l) {
				ans = i;
				break;
			}
		}
		printf("%d
", ans);
	}
	return 0;
}

[ZOJ-3524 Crazy Shopping] 完全背包+拓扑排序

http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=3524

就是一个无环单向图,给定起点和背包容量,从 u 到 v 获得的疲劳值是 两点间距离*当前背包容积。求取得最大值时的最小消耗。

需要注意拓扑排序出来的点不一定都能走,需要 vis 标记。然后转移的时候先转移前点过来的,再是之后的点(保证无后效性)。

v 写成了 u,wa 了一下午

#include <map>
#include <set>
#include <list>
#include <cmath>
#include <ctime>
#include <deque>
#include <stack>
#include <queue>
#include <bitset>
#include <cctype>
#include <cstdio>
#include <vector>
#include <string>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iomanip>
#include <numeric>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const double PI = acos(-1.0);
const double eps = 1e-6;
const int inf = 0x3f3f3f3f;
const int mod = 1e9 + 7;

const int maxn = 6e2+5;
const int maxm = 6e4+5;
const int maxw = 2e3+5;

struct Edge {
	int to, l, nxt;
}edge[maxm];

int c[maxn], val[maxn];
int in[maxn], head[maxn];
int topo[maxn], vis[maxn];
int dp[maxn][maxw];		// 走到 i 点,容量为 j 的 最大价值
int tp[maxn][maxw];		// 当最大价值为 dp[i][j] 时的 最小消耗
int n, m, w, x;
int MAXV, MINC;
int tot, cnt;

void init() {
	memset(in, 0, sizeof(in));
	memset(dp, 0, sizeof(dp));
	memset(vis, 0, sizeof(vis));
	memset(topo, 0, sizeof(topo));
	memset(head, -1, sizeof(head));
	memset(tp, inf, sizeof(tp));
	MAXV = 0;
	MINC = inf;
	tot = 0;
	cnt = 0;
}

void addedge(int u, int v, int l) {
	edge[tot].to = v;
	edge[tot].l = l;
	edge[tot].nxt = head[u];
	head[u] = tot++;
	in[v] ++;	
}

void Topo() {
	queue<int> q;
	for(int i = 1; i <= n; i++) {
		if(in[i] == 0) {
			q.push(i);
		}
	}
	while(!q.empty()) {
		int u = q.front();
		q.pop();
		topo[cnt++] = u;
		for(int i = head[u]; ~i; i = edge[i].nxt) {
			int v = edge[i].to;
			in[v] --;
			if(in[v] == 0) {
				q.push(v);
			}
		}
	}
}

int main() {
	while(~scanf("%d%d%d%d", &n, &m, &w, &x)) {
		init();
		for(int i = 1; i <= n; i++) {
			scanf("%d%d", &c[i], &val[i]);
		}
		for(int i = 1; i <= m; i++) {
			int u, v, l;
			scanf("%d%d%d", &u, &v, &l);
			addedge(u, v, l);
		}
		Topo();
		vis[x] = 1;
		dp[x][0] = tp[x][0] = 0;
		for(int i = 0; i < cnt; i++) {
			if(vis[topo[i]] == 0) {
				continue;
			}
			int u = topo[i];
			for(int j = c[u]; j <= w; j++) {
				if(dp[u][j] < dp[u][j-c[u]]+val[u]) {
					dp[u][j] = dp[u][j-c[u]] + val[u];
					tp[u][j] = tp[u][j-c[u]];
				}
				else if(dp[u][j] == dp[u][j-c[u]]+val[u]) {
					tp[u][j] = min(tp[u][j], tp[u][j-c[u]]);
				}
			}
			for(int j = head[u]; ~j; j = edge[j].nxt) {
				int v = edge[j].to;
				vis[v] = 1;
				for(int k = 0; k <= w; k++) {
					if(dp[v][k] < dp[u][k]) {
						dp[v][k] = dp[u][k];
						tp[v][k] = tp[u][k] + k*edge[j].l;
					}
					else if(dp[v][k] == dp[u][k]) {
						tp[v][k] = min(tp[v][k], tp[u][k]+k*edge[j].l);
					}
				}
			}
			for(int j = 0; j <= w; j++) {
				if((dp[u][j] > MAXV) || (dp[u][j]==MAXV && tp[u][j] < MINC)) {
					MAXV = dp[u][j];
					MINC = tp[u][j];
				}
			}
		}
		printf("%d
", MINC);
	}
	return 0;
}

[ZOJ-3662 Math Magic] 背包 + 状态压缩

http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=3662

把每个m的因子当作物品,要求我们随便选k个物品,使得这些物品的总和为n,Lcm为m。dp[i][j][k]表示选了i个物品,和为jlcm为k的方案数,具体做法是先预处理找出m的所有因子,这样不管怎么选,最后都有可能Lcm为m,免除很多无必要的计算,然后用这类背包的方法进行转移。

#include <map>
#include <set>
#include <list>
#include <cmath>
#include <ctime>
#include <deque>
#include <stack>
#include <queue>
#include <bitset>
#include <cctype>
#include <cstdio>
#include <vector>
#include <string>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iomanip>
#include <numeric>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const double PI = acos(-1.0);
const double eps = 1e-6;
const int inf = 0x3f3f3f3f;
const int mod = 1e9 + 7;

const int maxn = 1e3+5;

int a[maxn];
int lcm[maxn][maxn];
int dp[2][maxn][maxn];	// dp[i][j][k] : 第 i 个数,和为 j,lcm为 k
						// i 表示滚动数组(100-1000-1000 开不下)

int gcd(int x, int y) {
	return y == 0 ? x : gcd(y, x%y);
}

void init() {
	for(int i = 1; i <= 1000; i++) {
		for(int j = 1; j <= 1000; j++) {
			lcm[i][j] = i / gcd(i, j) * j;
		}
	}
}

int main() {
	init();
	int n, m, k;
	while(~scanf("%d%d%d", &n, &m, &k)) {
		memset(dp, 0, sizeof(dp));
		int cnt = 0;
		for(int i = 1; i <= m; i++) {
			if(m % i == 0) {
				a[cnt++] = i;
			}
		}
		dp[0][0][1] = 1;
		int f;
		for(int x = 1; x <= k; x++) {
			f = x % 2;
			for(int i = 0; i <= n; i++) {
				for(int j = 0; j <= m; j++) {
					dp[f][i][j] = 0;
				}
			}
			for(int i = x-1; i <= n; i++) {
				for(int j = 0; j < cnt; j++) {
					if(dp[f^1][i][a[j]] == 0) {
						continue;
					}
					for(int l = 0; l < cnt; l++) {
						int temp = lcm[a[j]][a[l]];
						if(a[l] + i > n || m % temp != 0) {
							continue;
						}
						dp[f][a[l]+i][temp] = (dp[f][a[l]+i][temp] + dp[f^1][i][a[j]]) % mod;
					}
				}
			}
		}
		printf("%d
", dp[f][n][m]);
 	}
	return 0;
}
原文地址:https://www.cnblogs.com/Decray/p/11191163.html