背个背包
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 }
3. 洛谷P5020-货币系统:虽然好像还有些什么其他的方法,但是我jio得反正用完全装满背包就对了啦~
(dp[m[i]]==1加深了对dp[]的理解好评,这种题其实要想到背包还是好做,但比赛时就看能不能想到了)
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 MS_I(x) memset(x,-inf,sizeof(x)) 11 #define MS(x) memset(x,0,sizeof(x)) 12 #define MSI(x) memset(x,inf,sizeof(x)) 13 using namespace std; 14 typedef long long ll; 15 typedef unsigned long long ull; 16 const int maxn = 25005; 17 using namespace std; 18 19 int m[105]; 20 int dp[maxn]; 21 22 int main() 23 { 24 int T; 25 scanf("%d", &T); 26 while (T--) 27 { 28 int n, ans = 0, tmax = 0; 29 scanf("%d", &n); 30 MS(m); 31 MS_I(dp); 32 dp[0] = 0; 33 for (int i = 1; i <= n; i++) 34 { 35 scanf("%d", &m[i]); 36 tmax = max(tmax, m[i]); 37 } 38 for (int j = 1; j <= n; j++) 39 for (int k = m[j]; k <= tmax; k++) 40 dp[k] = max(dp[k], dp[k - m[j]] + 1); 41 for (int i = 1; i <= n; i++) 42 if (dp[m[i]] == 1) 43 ans++; 44 printf("%d\n", ans); 45 } 46 return 0; 47 }
三、多重背包问题
完全背包和多重背包都有关于二进制降低时间复杂度的思路(虽然我不知道完全背包上述模板用了个啥)
所以多重背包我们好好学习下这个思想
1. Coins/ POJ || HDOJ : POJ上的数据比HDOJ上的数据要硬一些,本来李同学对于POJ上有别的方法的
但是我由于懒得看选择了更为深奥的楼教主算法——单调队列优化多重背包【Θ(VN)】
而HDOJ上的数据直接用二进制拆分板子转换为0-1背包和完全背包就可以了【Θ(V×∑log n[i])】
Code
① HDOJ:
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 MS_I(x) memset(x,-inf,sizeof(x)) 11 #define MS(x) memset(x,0,sizeof(x)) 12 #define MSI(x) memset(x,inf,sizeof(x)) 13 using namespace std; 14 typedef long long ll; 15 typedef unsigned long long ull; 16 const int maxn = 1e5 + 5; 17 using namespace std; 18 int dp[maxn]; 19 int a[105], c[105]; 20 21 int main() 22 { 23 int n, m; 24 while (scanf("%d %d", &n, &m) && (n != 0 || m != 0)) 25 { 26 for (int i = 1; i <= n; i++) 27 scanf("%d", &a[i]); 28 for (int i = 1; i <= n; i++) 29 scanf("%d", &c[i]); 30 MS_I(dp); 31 dp[0] = 0; 32 for (int i = 1; i <= n; i++) 33 { 34 if (c[i] * a[i] >= m) 35 for (int j = a[i]; j <= m; j++) //完全背包 36 dp[j] = max(dp[j], dp[j - a[i]] + 1); 37 else 38 { 39 for (int k = 1; k < c[i]; k <<= 1) //binary thought 40 { 41 for (int j = m; j >= a[i] * k; j--) //0-1背包 42 dp[j] = max(dp[j], dp[j - a[i] * k] + 1); 43 c[i] -= k; 44 } 45 if (c[i] > 0) 46 for (int j = m; j >= a[i] * c[i]; j--) //0-1背包 47 dp[j] = max(dp[j], dp[j - a[i] * c[i]] + 1); 48 } 49 } 50 int ans = 0; 51 for (int i = 1; i <= m; i++) 52 { 53 if (dp[i] >= 0) 54 ans++; 55 } 56 printf("%d\n", ans); 57 } 58 59 return 0; 60 }
② POJ:(不知道为什么直接套模板(关键是我还不懂)更加超时,当然简化之后还是过了的)(注释贼多系列+)
强烈要求阿萌本人日后多看这几个博客(啊啊啊楼教主我真的弄不懂啊)👉 CSDN, cnblogs,其他博客1,其他博客2
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 #include <queue> 10 #define inf 0X7f7f7f7f 11 #define MS_I(x) memset(x,-inf,sizeof(x)) 12 #define MS(x) memset(x,0,sizeof(x)) 13 #define MSI(x) memset(x,inf,sizeof(x)) 14 using namespace std; 15 typedef long long ll; 16 typedef unsigned long long ull; 17 const int maxn = 1e5 + 5; 18 using namespace std; 19 20 bool dp[maxn], tqueue[maxn];//储存bool值 21 int a[105], c[105]; 22 23 int main() 24 { 25 int n, m; 26 while (scanf("%d %d", &n, &m) && (n != 0 || m != 0)) 27 { 28 for (int i = 1; i <= n; i++) 29 scanf("%d", &a[i]); 30 for (int i = 1; i <= n; i++) 31 scanf("%d", &c[i]); 32 MS(dp); 33 MS(tqueue);//单调队列初始化 34 35 36 dp[0] = true; 37 for (int i = 1; i <= n; i++)//枚举物品种类 38 { 39 if (c[i] == 1)//0-1背包 40 { 41 for (int j = m; j >= a[i]; j--) 42 if (dp[j - a[i]]) 43 dp[j] = 1; 44 } 45 else if (c[i] * a[i] >= m)//完全背包 46 { 47 for (int j = a[i]; j <= m; j++) 48 if (dp[j - a[i]]) 49 dp[j] = 1; 50 } 51 else 52 { 53 for (int j = 0; j < a[i]; j++)//枚举余数 54 { 55 int haveT = 0, st = 0, ed = -1;//st,ed 为单调队列的开始和结尾(这里算又一次初始化)(并且他们都喜欢这样) 56 //haveT 用于判断队列中是否有一个true值 57 58 for (int k = j; k <= m; k += a[i])//完全背包model,步长为a[i] 59 //注释写了完全背包 model, 但实际上写成 01 背包 model 也是可以的, 结果与之无关 60 //但写成 01 背包 model 更加合适, 毕竟分组背包的经典解法是转化为 01 背包 61 { 62 if (ed - st == c[i]) //若队列大小达到指定值,第一个元素X1出队。 63 haveT -= tqueue[st++]; 64 65 //元素X2进队,这个地方入队如果是标准nomotone queue alg的话应该会以ele = f[k] - i * w再将ele入队的形式出现(标准模式请见Blog) 66 tqueue[++ed] = dp[k]; //queue[++ed] = dp[v]可以减少判断 67 haveT += dp[k]; 68 69 //haveT为1的条件为【last】haveT + x2 (- x1) >= 0 70 if (haveT)//如果队列中有一个true,则dp[k]为max()即为true 71 dp[k] = 1; 72 } 73 } 74 } 75 } 76 int ans = 0; 77 for (int i = 1; i <= m; i++) 78 ans += dp[i]; 79 printf("%d\n", ans); 80 } 81 return 0; 82 }
四、混合背包问题
首先我们把上述三种背包的代码以函数的形式表现出来方便如背包九讲中的伪代码一般(我实在不想写背包了(望向DFS与BFS))
Code
1. 0-1背包
void completePack(int cost, int weight) { for (int v = V; v >= cost; v++) dp[v] = max(dp[v], dp[v - cost] + weight); }
2. 完全背包
void completePack(int cost, int weight) { for (int v = cost; v <= V; v++) dp[v] = max(dp[v], dp[v - cost] + weight); }
3. 多重背包
void multiplePack(int cost, int weight, int amount) { if (cost * amount >= V) completePack(cost, weight); else { int k = 1; while (k < amount) { zeroOnePack(k * cost, k * weight); amount -= k; k = 2 * k; } zeroOnePack(amount * cost, amount * weight); } }
五. 分组背包
洛谷P1757:分组背包的板子题,由于三层循环相互影响不是很方便函数化就和一般的教程一样分开啦~
也像一般教程一样强调一下循环嵌套顺序——先遍历所有组k再遍历V→0最后在组内遍历i进行if判断,然后pair和vector一起的注意事项见代码
1 #pragma warning (disable:4996) 2 #include <algorithm> 3 #include <iostream> 4 #include <iomanip> 5 #include <cstdio> 6 #include <cmath> 7 #define inf 0X7f7f7f7f 8 #define MS_I(x) memset(x,-inf,sizeof(x)) 9 #define MS(x) memset(x,0,sizeof(x)) 10 #define MSI(x) memset(x,inf,sizeof(x)) 11 using namespace std; 12 typedef long long ll; 13 typedef unsigned long long ull; 14 const int maxn = 1e3 + 5; 15 using namespace std; 16 17 int kn, V, n; 18 int dp[maxn]; 19 int a[maxn], b[maxn]; 20 vector<pair<int, int> > c[maxn];//注意vector和pair右边的> >不要放在一起 21 22 int main() 23 { 24 scanf("%d %d", &V, &n); 25 int numberG; 26 for (int i = 1; i <= n; i++) 27 { 28 scanf("%d %d %d", &a[i], &b[i], &numberG); 29 c[numberG].push_back(make_pair(a[i], b[i])); 30 kn++; 31 } 32 MS(dp); 33 for (int k = 1; k <= kn; k++) 34 for (int j = V; j >= 0; j--) 35 for (int i = 0; i < c[k].size(); i++) 36 if (j >= c[k][i].first) 37 dp[j] = max(dp[j], dp[j - c[k][i].first] + c[k][i].second); 38 39 cout << dp[V] << endl; 40 return 0; 41 }
END
目前我们就先将背包学习到这啦~(我都要吐了),今后有遇到背包最好要认出来然后做出来呀🙈,做不出再来这里跪博客了哈哈