P5322 [BJOI2019]排兵布阵(分组背包好题)
题目描述:
小 C 正在玩一款排兵布阵的游戏。在游戏中有 nn 座城堡,每局对战由两名玩家来争夺这些城堡。每名玩家有 mm 名士兵,可以向第 ii 座城堡派遣 a_iai 名士兵去争夺这个城堡,使得总士兵数不超过 mm。
如果一名玩家向第 ii 座城堡派遣的士兵数严格大于对手派遣士兵数的两倍,那么这名玩家就占领了这座城堡,获得 ii 分。
现在小 C 即将和其他 ss 名玩家两两对战,这 ss 场对决的派遣士兵方案必须相同。小 C 通过某些途径得知了其他 ss 名玩家即将使用的策略,他想知道他应该使用什么策略来最大化自己的总分。
由于答案可能不唯一,你只需要输出小 C 总分的最大值。
输入格式
输入第一行包含三个正整数 s,n,ms,n,m,分别表示除了小C以外的玩家人数、城堡数和每名玩家拥有的士兵数。
接下来 ss 行,每行 nn 个非负整数,表示一名玩家的策略,其中第 ii 个数 a_iai 表示这名玩家向第 ii 座城堡派遣的士兵数。
输出格式
输出一行一个非负整数,表示小 C 获得的最大得分。
输入输出样例
1 3 10 2 2 6
3
2 3 10 2 2 6 0 0 0
8
思路:这题大家一看就知道是个分组背包,俺也一样,然后就对每个分组进行dp,然后再总体松弛一下呗
代码:
#include<bits/stdc++.h> using namespace std; int dp[101][20005]; int res[20005]; int ss[105][105]; int s, n, m; int main() { //freopen("test.txt", "r", stdin); scanf("%d%d%d", &s, &n, &m); for (int i = 1; i <= s; i++) { for (int j = 1; j <= n; j++) { int t; scanf("%d", &t); t = t * 2 + 1; for (int k = m; k >= t; k--) { dp[j][k] = max(dp[j][k], dp[j][k - t] + j); } } } for (int i = 1; i <= n; i++) { for (int j = m; j >= 1; j--) { for (int k = j; k >= 1; k--) { res[j] = max(res[j], res[j - k] + dp[i][k]); } } } cout << res[m] << endl; return 0; }
啪的一下就超时了,很快很快啊,怎么肥事呢...每个分组的不同容量下的价值也不同,不可能降低维度啊,一定是要三维的
考虑其他方面,我们意识到,每个分组它在不同容量下的价值只是当前容量下能加入的价值加上前一容量能取到的价值,
所以咱可以把对每个分组的dp哪里的第三维通过On^2的前缀和替换来求,于是又有了代码:
#include<bits/stdc++.h> using namespace std; int dp[101][20005]; int res[40005]; int ss[105][40005]; int s, n, m; int main() { //freopen("test.txt", "r", stdin); scanf("%d%d%d", &s, &n, &m); for (int i = 1; i <= s; i++) { for (int j = 1; j <= n; j++) { int t; scanf("%d", &t); t = t * 2 + 1; ss[j][t] += j; } } for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { dp[i][j] = dp[i][j - 1]; if (ss[i][j]) { dp[i][j] += ss[i][j]; } } }//前缀和优化求解不同分组在不同容量下的最大价值 for (int i = 1; i <= n; i++) { for (int j = m; j >= 1; j--) { for (int k = j; k >= 1; k--) { res[j] = max(res[j], res[j - k] + dp[i][k]); } } } cout << res[m] << endl; return 0; }
啪的一下,又超时了....但是比之前的代码多对了两个点.
啊这..咋办欸,又找呗,既然我们都想到前缀和了,我们是不是也可以想到,
其实每个分组的最大价值取值是断开的,是许多不同容量下的取值,比如
我们求到一个分组在取容量1的时候可以获取3的价值,取容量3的时候可以获取6的价值
按照我们上面的算法,假设m=5,我们去求了容量为1 2 3 4 5容量下获取的价值,分别为3 3 9 9 9复杂度O(m)
其实没必要,我们只需要把容量为1和3当作一个小物品,然后去dp就行了, 复杂度少很多
于是就有了AC代码;
#include<bits/stdc++.h> using namespace std; vector<pair<int,int>>dp[101]; int res[40005]; int ss[105][40005]; int s, n, m; int main() { //freopen("test.txt", "r", stdin); scanf("%d%d%d", &s, &n, &m); for (int i = 1; i <= s; i++) { for (int j = 1; j <= n; j++) { int t; scanf("%d", &t); t =t*2+1; ss[j][t] += j; } } for (int i = 1; i <= n; i++) { int sum = 0; for (int j = 1; j <= m; j++) { if (ss[i][j]) { sum += ss[i][j]; dp[i].push_back({j,sum});//看成小物件 } } } for (int i = 1; i <= n; i++) { for (int j = m; j >= 1; j--) { for (int h = 0; h < dp[i].size()&&dp[i][h].first<=j;h++) { pair<int, int>k = dp[i][h]; res[j] = max(res[j], res[j - k.first] + k.second); } } } cout << res[m] << endl; return 0; }
这才叫 分 组 背 包 !
呜呜呜呜....,看完题解我又回来了,呜呜呜,原来s就是一个分组的所有可能选择个数的嘛,题解好简短,呜呜呜
#include<bits/stdc++.h> using namespace std; int res[40005]; int ss[105][40005]; int a[105][105]; int s, n, m; int main() { //freopen("test.txt", "r", stdin); scanf("%d%d%d", &s, &n, &m); for (int i = 1; i <= s; i++) { for (int j = 1; j <= n; j++) { scanf("%d", &a[j][i]); } } for (int i = 1; i <= n; i++) { sort(a[i] + 1, a[i] + s + 1); //对每个组排序,方便后面选择 } for (int i = 1; i <= n; i++) { for (int j = m; j >= 1; j--) { for (int k = 1; k<=s;k++) {//每个组进行选择 if (j > 2 * a[i][k])//该组前k个都能选 res[j] = max(res[j], res[j - 2*a[i][k]-1]+i*k); else break;//不能选了,直接退出 } } } cout << res[m] << endl; return 0; }