zoukankan      html  css  js  c++  java
  • 暑假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;
    }
    
  • 相关阅读:
    Django学习(二) Django框架简单搭建
    Django学习(一) Django安装配置
    Python学习(一) Python安装配置
    注册第一天,纪念一下
    小程序笔记
    详解HTML5中的进度条progress元素简介及兼容性处理
    服务管理
    yum
    管道,输出,管道,重定向,grep
    VIM
  • 原文地址:https://www.cnblogs.com/Decray/p/11191163.html
Copyright © 2011-2022 走看看