OPEN
2019-09-14的沈阳网络赛过去很多天了(8天),现在才来针对里面的C题Dawn-K's water虽然有点晚,但是前有某数论降幂,后有上海网络赛签到提前缀和崩掉······
不管怎么说,由于这次完全背包题的爆炸,我们终于决定要向DP迈进了,为了明年的蓝桥杯和ICPC/CCPC加油?!
一、0/1背包问题
1. 洛谷P1060:一道板子题,0/1背包昨晚看得脑壳疼还没看懂,可能是大脑这天用太久了,今早看李煜东的书终于大雾?,感动
我比较害怕以后又忘了原理,但是自己解释得也不一定比李同学好(主要是懒)
以后忘了的话就扇自己一巴掌然后再看看李的书吧╮(╯▽╰)╭(网上博客还真是没看懂)
但事实是这个板子不是最优!!!emmm李的书还是不太全这个,自己盘里的PDF更细一些
最优的板子在这个博客里找叭~(常数优化见洛谷最近提交码)?
Code
1 #pragma warning (disable:4996) 2 #include <algorithm> 3 #include <iostream> 4 #include <iomanip> 5 #include <cstring> 6 #include <string> 7 #include <cstdio> 8 #include <cmath> 9 #define MS(x) memset(x,0,sizeof(x)) 10 using namespace std; 11 typedef long long ll; 12 typedef unsigned long long ull; 13 const int maxn = 1e8 + 5; 14 using namespace std; 15 16 int v[30], w[30], f[30005]; 17 //0-1背包,钱数代表容量,v价格*w重要度代表价值 18 int main() 19 { 20 int N, m; 21 scanf("%d %d", &N, &m); 22 int vi, wi; 23 for (int i = 1; i <= m; i++) 24 { 25 scanf("%d %d", &v[i], &w[i]); 26 w[i] *= v[i]; 27 } 28 for (int i = 1; i <= m; i++) 29 { 30 for (int j = N; j >= v[i]; j--) 31 f[j] = max(f[j - v[i]] + w[i], f[j]); 32 } 33 printf("%d", f[N]); 34 return 0; 35 }
2. 洛谷P1064: 主角还是金明小朋友,0/1背包的拓展题,可能会让人望而却步,事实上储存思路好了就一样easy
Code
1 #pragma warning (disable:4996) 2 #include <algorithm> 3 #include <iostream> 4 #include <vector> 5 #include <iomanip> 6 #include <cstring> 7 #include <string> 8 #include <cstdio> 9 #include <map> 10 #include <stack> 11 #include <cmath> 12 #define MS(x) memset(x,0,sizeof(x)) 13 using namespace std; 14 typedef long long ll; 15 typedef unsigned long long ull; 16 const int maxn = 1e8 + 5; 17 using namespace std; 18 int fw[100][3], fv[65][3], f[33000]; 19 int main() 20 { 21 int N, m; 22 scanf("%d %d", &N, &m); 23 int v, w, q; 24 for (int i = 1; i <= m; i++) 25 { 26 scanf("%d %d %d", &w, &v, &q); 27 if (!q)//the main part 28 { 29 fw[i][q] = w; 30 fv[i][q] = w * v; 31 } 32 else if (!fw[q][1])//1st attachment 33 { 34 fw[q][1] = w; 35 fv[q][1] = w * v; 36 } 37 else//2ed attachment 38 { 39 fw[q][2] = w; 40 fv[q][2] = w * v; 41 } 42 } 43 for (int i = 1; i <= m; i++) 44 { 45 for (int j = N; j >= 0; j--) 46 { 47 //main part 48 if (j >= fw[i][0]) 49 f[j] = max(f[j], f[j - fw[i][0]] + fv[i][0]); 50 //main + 1st 51 if (j >= (fw[i][0] + fw[i][1])) 52 f[j] = max(f[j], f[j - fw[i][0] - fw[i][1]] + fv[i][1] + fv[i][0]); 53 //main + 2ed 54 if (j >= (fw[i][0] + fw[i][2])) 55 f[j] = max(f[j], f[j - fw[i][0] - fw[i][2]] + fv[i][2] + fv[i][0]); 56 //main + 1st + 2ed 57 if (j >= (fw[i][0] + fw[i][1] + fw[i][2])) 58 f[j] = max(f[j], f[j - fw[i][0] - fw[i][1] - fw[i][2]] + fv[i][0] + fv[i][1] + fv[i][2]); 59 } 60 } 61 printf("%d\n", f[N]); 62 return 0; 63 }
二、完全背包问题
1. Dawn-K's water:开头肯定就是沈阳网络赛这道啦!(什么?先给板子题?是男人就直接正面干!)
Code
1 #pragma warning (disable:4996) 2 #include <algorithm> 3 #include <iostream> 4 #include <iomanip> 5 #include <cstring> 6 #include <string> 7 #include <cstdio> 8 #include <cmath> 9 #define inf 0X7f7f7f7f 10 #define MS0(x) memset(x,0,sizeof(x) 11 #define MSI(x) memset(x,inf,sizeof(x)) 12 using namespace std; 13 typedef long long ll; 14 typedef unsigned long long ull; 15 const int maxn = 1e8 + 5; 16 using namespace std; 17 int w[1005], v[1005]; 18 ll f[10005];//注意长度int你就等着WA嘤 19 int main() 20 { 21 int n, m; 22 while (scanf("%d %d", &n, &m) != EOF) 23 { 24 MSI(f);//每次更新f[],由于是背包取价值最小,故以inf初始化 25 f[0] = 0; 26 for (int i = 1; i <= n; i++) 27 scanf("%d %d", &v[i], &w[i]); 28 for (int i = 1; i <= n; i++) 29 for (int j = w[i]; j <= 10000; j++) 30 f[j] = min(f[j], f[j - w[i]] + v[i]);//同取价值最小用min() 31 //得到全部f[]后为了求出购买的重量(>=背包容量m),采用从m到最大值枚举的办法求出最小价值并得到此时的重量i 32 ll ans = inf, ans2 = 0; 33 for (int i = m; i <= 10000; i++) 34 { 35 if (ans >= f[i]) 36 { 37 ans = f[i]; 38 ans2 = i; 39 } 40 } 41 printf("%lld %lld\n", ans, ans2); 42 } 43 return 0; 44 }
这个虽说也有模板成分,但是由此出来的模板拓展要记得理解嗷~
万岁我用三天就解决了?比数论某降幂轻松多了
2. Jury Compromise:这道题要是日后能自个儿码出来我觉得那时我还是很强的,网上有很多错解,参考博客:CNBLOGS,CSND
代码码了3天,算是很良心了,知识精化在注释里,仔细看看挺好,事实上我觉得这玩意儿算01背包hh(虚脱orz)
Code
1 #pragma warning (disable:4996) 2 #include <algorithm> 3 #include <iostream> 4 #include <vector> 5 #include <iomanip> 6 #include <cstring> 7 #include <string> 8 #include <cstdio> 9 #include <map> 10 #include <stack> 11 #include <cmath> 12 #define inf 0X7f7f7f7f 13 #define MS_I(x) memset(x,-inf,sizeof(x)) 14 #define MS(x) memset(x,0,sizeof(x)) 15 #define MSI(x) memset(x,inf,sizeof(x)) 16 using namespace std; 17 typedef long long ll; 18 typedef unsigned long long ull; 19 const int maxn = 1e8 + 5; 20 using namespace std; 21 int p[201], d[201]; 22 int f[201][801];//20*20*2//我们把m作为v1,p-d作为V2,并考虑到负数下标的问题,所以设大小为800起 23 //f[i][j] = k表示在到已经选择了i个候选人且(P-D)Weiht为j时P+D的Value 24 int ans[21], path[201][21][801];//该题网上Blog所有path为二维而非三维的题解全是错的了 25 int main() 26 { 27 int n, m; 28 int t = 0; 29 while (scanf("%d %d", &n, &m) && (n != 0 || m != 0)) 30 { 31 t++; 32 //记得更新嗷 33 MS_I(f); 34 MS(p); 35 MS(d); 36 MS(ans); 37 //path三维路径目前题解里有三维数组和二维+vector容器(超省内存,省一半时间)两种存储办法,这里我们用经典三维数组更能直观体现本质三维dp,故将vector注释 38 MS(path); 39 //vector<int> path[21][801]; 40 int range = m * 20; 41 f[0][range] = 0;//原f[0][0]=0在V2下标问题的处理下也随之变成f[0][m*20] = 0 42 for (int i = 1; i <= n; i++) 43 scanf("%d %d", &p[i], &d[i]); 44 for (int i = 1; i <= n; i++) 45 for (int j = m; j >= 1; j--)//逆序(每人最多选一次)(01背包实锤) 46 //(题解里这个是f[j][k + p[i] - d[i]] <= f[j - 1][k] + p[i] + d[i],省去了我这里多出的越界判定,算是一个很好的小优化 47 //(不过我是为了按传统套路来硬是WA了n遍才终于改对了!) 48 //关于 k = p[i] - d[i] && k - p[i] + d[i] <= 2 * range是由于k - p[i] + d[i]可能越界因此不存在该情况加的判定 49 for (int k = p[i] - d[i]; k <= range * 2; k++) 50 { 51 path[i][j][k] = path[i - 1][j][k]; 52 //而根据之前初始化-inf,故f[j - 1][k - p[i] + d[i]] < 0亦说明不存在该情况排去 53 if (f[j - 1][k - p[i] + d[i]] >= 0 && k - p[i] + d[i] <= 2 * range)//后者没加就WA哭 54 { 55 if (f[j][k] <= f[j - 1][k - p[i] + d[i]] + p[i] + d[i]) 56 { 57 f[j][k] = f[j - 1][k - p[i] + d[i]] + p[i] + d[i]; 58 path[i][j][k] = i; 59 } 60 } 61 } 62 int minIndex, mk; 63 //得到abs(P - D)最小值k 64 for (mk = 0; mk <= range; mk++) 65 if (f[m][range - mk] >= 0 || f[m][range + mk] >= 0) 66 break; 67 (f[m][range - mk] > f[m][range + mk]) ? minIndex = range - mk : minIndex = range + mk; 68 //目前f[m][minIndex] = P + D; minIndex = P - D + 20 * m 69 //但是不能以为P = (f[m][minIndex] + k) / 2, D同理,因为k自带绝对值属性...所以还是老老实实... 70 cout << "Jury #" << t << endl << "Best jury has value "; 71 cout << (f[m][minIndex] + minIndex - range) / 2 << " for prosecution and value "; 72 cout << (f[m][minIndex] - minIndex + range) / 2 << " for defence:" << endl; 73 //以下三行为vector方法代码(超短!)由于按照顺序遍历,最后的路径本身就是有序的,所以直接输出就可以了 74 //int tmp = m; 75 //for (int i = 0; i < m; i++) 76 // cout << " " << path[m][minIndex][i]; 77 //下面开始演示三维数组path递归,事实上很好理解的嘛,从后往前 78 for (int i = n, j = m, k = minIndex; j >= 1;) 79 { 80 int r = path[i][j][k]; 81 ans[j] = r; 82 k -= p[r] - d[r]; 83 j--; 84 i = path[r - 1][j][k]; 85 } 86 for (int i = 1; i <= m; i++) 87 cout << " " << ans[i]; 88 printf("\n\n"); 89 } 90 return 0; 91 }
三、多重背包问题