背包专题
一、 01背包
1.题目
1) HDU 4526
地址:http://acm.hdu.edu.cn/showproblem.php?pid=4526
题意:N个人要在S分钟内乘K俩车到达比赛现场,每辆车Ki分钟到达,剩余Zi个座位,不管几个人上一辆车,费用都是D,对每一辆车可选择做还是不做,不做产生等待费用,等待费用是时间乘以人数,问最后在所有人能到达比赛现场的情况下,话费最少的钱是多少?
解法:对第i俩车,可选择做还是不做,dp[i][j] 表示前i辆车做了j人所需要的最小费用,典型的01背包问题。
dp方程:
dp[i][j] = min(dp[i-1][j] + (ptime[i] - ptime[i-1]) * (N-j) , dp[i-1][j - k] +( N - j + k) * (ptime[i] - ptime[i-1]) + D);
令我羞愧的是,比赛时我设的i从0开始,初始dp[0][0] = ptime[0] * N ,其他dp元素的值为inf, 一直WA~~~
将dp[0][0] = 0,就AC了~~~不解~~以后还是都从1开始吧~~惨痛的教训,认真分析方程含义,处理各种边界情况,特别是初始值问题
View Code
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <algorithm> using namespace std; const int inf = 0x7f7f7f7f; #define MAXN 110 int ptime[110], seat[110], dp[110][510]; /* memeset(dp, 0x7f, sizeof(dp)); dp[i][0] = N * time[i] dp[i][v], 表示到第i辆车时,累计已经有v个人上车了 for(int i = 1; i <= seat[0]; i++) dp[0][i] = D + ( N - i ) * time[1]; dp[i][v] = min(dp[i-1][v - seat[i]] + (N - v + seat[i] )* (time[i] - time[i-1]) + D, dp[i-1][v] + (time[i] - time[i-1]) * (N-v) ) */ int main( ) { int N,K,D,S,T; scanf("%d",&T); while(T--) { scanf("%d%d%d%d",&N,&K,&D,&S); memset(ptime, 0, sizeof(ptime)); memset(seat, 0, sizeof(seat)); memset(dp, 0x7f, sizeof(dp)); dp[0][0] = 0; for(int i = 1; i <= K; ++i) { scanf("%d%d",&ptime[i], &seat[i]); } for(int i = 1; i <= K; ++i) dp[i][0] = ptime[i] * N; for(int i = 1; i <= K; ++i) { for(int j = 0; j<= N; ++j) { dp[i][j] = dp[i-1][j] + (ptime[i] - ptime[i-1]) * (N - j); for(int k = 0; k <= seat[i]; ++k) { if( j - k >= 0 ) { dp[i][j] = min(dp[i-1][j - k] +( N - j + k) * (ptime[i] - ptime[i-1]) + D,dp[i][j]); } } } } if( dp[K][N] == inf ) puts("impossible"); else printf("%d\n",dp[K][N]); } return 0; }
另外一种简单写法,此外还可以优化成一维dp
View Code
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <algorithm> using namespace std; const int inf = 0x3f3f3f3f; int main( ) { int T, N, K, D, S; scanf("%d",&T); int ptime[110],seat[110], dp[110][110]; while(T--) { scanf("%d%d%d%d",&N,&K,&D,&S); for(int i = 1; i <= K; ++i) scanf("%d%d",&ptime[i],&seat[i]); memset(dp,0x3f,sizeof(dp)); dp[0][0] = 0; for(int i = 1; i <= K; i++) { for(int j = 0; j <= N; j++) { dp[i][j] = dp[i-1][j]; for(int k = 1; k <= seat[i]; ++k) { if( j - k >= 0 ) dp[i][j] = min(dp[i-1][j-k] + D + k * ptime[i], dp[i][j]); } } } if( dp[K][N] == inf ) puts("impossible"); else printf("%d\n",dp[K][N]); } return 0; }
2. coins
题意:小明有N种不同分值的硬币,每个分值的硬币数量为Ci,手表费用不超过M, 问小明的这些硬币能组成多少种钱(1-M)。
算法:
1.动态规划解法
对于每一种分值的硬币,可选择取于不取,还可以选择取的数量,用二进制将数量转为一个单一物品,比如16 = 1 + 2 + 4 + 8 + 1,
数量16的物品,可以由五个物品组成,总的价钱不能超过M。
1 ≤ N ≤ 100 M≤ 100000
dp[M]表示硬币组成钱的和为M时,是否可以组成M钱。
最后从1遍历到M,统计能组成的钱数。
最坏时间复杂度为O(N * M)
代码:
View Code
#include <stdio.h> #include <string.h> #include <stdlib.h> int v[110],num[110]; int dp[100010]; int value[2000]; int max(int x, int y) { return x > y ? x : y; } int main( ) { int n,m; while( scanf("%d%d",&n,&m), n||m ) { for(int i = 1; i <= n; ++i) scanf("%d",&v[i]); for(int i = 1; i <= n; ++i) scanf("%d",&num[i]); memset(dp, 0, sizeof(dp)); //将num[i]插为二进制表示 //16 = 1 2 4 8 1 int cnt = 0; for(int i = 1; i <= n; ++i) { int t = 1, left = num[i],flag = 0; while( left >= t ) { //printf("t = %d\n",t); if( t * v[i] > m ) { flag = 1; break; } value[++cnt] = t * v[i]; left -= t; t <<= 1; } //printf("left = %d\n",left); if( left && !flag ) value[++cnt] = left * v[i]; } //对于value中每个物品,可以选一个,也可以选多个 //dp[v] = dp[v - value[i] ] dp[0] = 1; for(int i = 1; i <= cnt; i++) { for(int V = m; V >= value[i]; --V) { if( V >= value[i] && dp[V-value[i]] != 0 ) dp[V] = dp[V - value[i] ]; //else //dp[V] = dp[V-1]; } } cnt = 0; for(int i = 1; i <= m; i++) if( dp[i] ) ++cnt; printf("%d\n",cnt); } return 0; }
母函数解法:
(1+x1 + x1^2 + x1^3 + ..) ....(1 + xn + x^2n + x^3n)
xn表示x的n次方。
统计最后x的次数小于等于M,且系数不为0的个数,就是能组成的钱数。
最坏时间复杂度为O( N * M * MAX(ci) ) 果断TLE
代码:
View Code
#include <stdio.h> #include <stdlib.h> #include <string.h> #define MAXN 102000 int c1[MAXN], c2[MAXN]; int v[MAXN], num[MAXN], sum[MAXN]; /* (1+x1 + x1^2 + x1^3 + ..) ....(1 + xn + x^2n + x^3n) */ int solve(int n, int m) { //memset(sum, 0, sizeof(sum)); //for(int i = 1; i <= n; i++) // sum[i] += sum[i-1] + v[i] * num[i]; //统计前i项时x最高次数 for(int i = 0; i <= m; i++) { c1[i] = 0; c2[i] = 1; //每个括号中系数的值 } //c1[0] = 1; int maxn = 0, temp = 0; for(int i = 1; i <= n; ++i) //n个括号,n 轮运算 { temp = maxn; for(int j = 0; j <= temp && j <= m; j++) //枚举前i-1个括号所得值 { //int ans = num[i] * v[i]; for(int k = 0, l = 0; l <= num[i] && (j + k) <= m; k += v[i], l++) { c1[ j + k ] += c2[k]; maxn = (j + k) > maxn ? (j + k) : maxn; } //c2[ j ] += c1[j]; } } int cnt = 0; for(int i = 1; i <= m; i++) if( c1[i] ) ++cnt; return cnt; } int main( ) { int n,m; while( scanf("%d%d",&n,&m), n||m ) { for(int i = 1; i <= n; ++i) scanf("%d",&v[i]); for(int i = 1; i <= n; ++i) scanf("%d",&num[i]); printf("%d\n",solve(n,m)); } return 0; }

浙公网安备 33010602011771号