「CSA49」Card Collecting Game
「CSA49」Card Collecting Game
题目大意:有 \(n\) 种卡片,每种有 \(b_i\) 张,如果一个人集齐 \(k\) 张第 \(i\) 种卡片,那么其能获得的得分是 \(\lfloor\frac{k}{a_i}\rfloor \times c_i\) ,现在A要将所有卡片分成大小相同两堆,保证总卡片数量是偶数。A与B玩游戏,第一堆A先手轮流取,第二堆B先手轮流取,A要最大化自己的得分,B要最小化A的得分,求A能得到的最大得分。
解题思路:首先放在两堆的卡片都可以模 \(2a_i\) ,因为每 \(2a_i\) 双方必定都会各取 \(a_i\) 张取光得到 \(c_i\) 的贡献,对于剩下的部分,如果不足 \(2a_i-1\) ,那么不可能得到任何贡献 ,否则先手能得到 \(2a_i-1\) 的贡献,然后交换先后手。也就是说将每堆剩下 \(2a_i-1\) 的卡片按照 \(c_i\) 排序后,第一堆A能拿到 \(1,3,5..\) 种的贡献,第二堆A能拿到 \(2,4,6\) 种的贡献。然后就可以根据此设计 \(dp(i,j,x,y)\) 表示排完序后前 \(i\) 种卡片,放在第一堆共 \(i\) 张,当前第一堆中剩下 \(2a_i-1\) 的卡片摆了奇偶性为 \(x\) 堆,第二堆中摆了奇偶性为 \(y\) 堆,然后枚举当前这一种在第一堆种放多少计算贡献转移,复杂度是 \(O((\sum_{b_i})^2)\)。
实际上贡献只与当前该种卡片放到第一堆和第二堆中的数量除以 \(2a_i\) 的商和余数有关,商的部分无论怎样贡献都会被计算进去商-1份是常量,所以只需要考虑剩下的部分要放多少即可,但是这样的话就不能知道当前状态配上之前的常量能否凑成刚好两堆一样了,于是再对商的部分做一个多重背包判断,复杂度 \(O(\sum_{a_i}\sqrt{\sum_{b_i}}+(\sum_{a_i})^2)\)
code
/*program by mangoyang*/
#include<bits/stdc++.h>
#define inf (0x7f7f7f7f)
#define Max(a, b) ((a) > (b) ? (a) : (b))
#define Min(a, b) ((a) < (b) ? (a) : (b))
typedef long long ll;
using namespace std;
template <class T>
inline void read(T & x){
int ch = 0, f = 0; x = 0;
for(; !isdigit(ch); ch = getchar()) if(ch == '-') f = 1;
for(; isdigit(ch); ch = getchar()) x = x * 10 + ch - 48;
if(f) x = -x;
}
const int N = 4005;
bitset<250005> f;
int dp[2][8005][2][2], lim[N], tot[N], yu[N], sum, ans, sumA, n;
struct Node{ int a, b, c; } s[N];
inline bool cmp(Node A, Node B){ return A.c > B.c; }
inline void up(int &x, int y){ if(y >= x) x = y; }
int main(){
read(n);
for(int i = 1; i <= n; i++)
read(s[i].a), read(s[i].b), read(s[i].c);
sort(s + 1, s + n + 1, cmp);
for(int i = 1; i <= n; i++){
lim[i] = (s[i].b < 2 * s[i].a) ? 0 : (s[i].b / (2 * s[i].a)) - 1;
yu[i] = s[i].b - 2 * s[i].a * lim[i], ans += s[i].c * lim[i];
tot[s[i].a] += lim[i], sum += s[i].b, sumA += yu[i];
}
f[0] = 1;
for(int i = 1; i <= 2000; i++)
for(int j = 1; j <= tot[i]; j <<= 1) f |= f << (j * 2 * i);
memset(dp, -1, sizeof(dp));
dp[0][0][0][0] = 0;
for(int i = 0, o = 0; i < n; i++, o ^= 1){
memset(dp[o^1], -1, sizeof(dp[o^1]));
for(int j = 0; j <= sumA; j++)
for(int a = 0; a < 2; a++)
for(int b = 0; b < 2; b++) if(~dp[o][j][a][b]){
for(int k = 0; k <= yu[i+1]; k++){
int ta = k, tb = yu[i+1] - k, tmp = ta / (2 * s[i+1].a) + tb / (2 * s[i+1].a);
int na = a, nb = b;
ta %= (2 * s[i+1].a), tb %= (2 * s[i+1].a);
if(ta == 2 * s[i+1].a - 1) tmp += (++na == 1);
if(tb == 2 * s[i+1].a - 1) tmp += (++nb == 2);
up(dp[o^1][j+k][na&1][nb&1], dp[o][j][a][b] + tmp * s[i+1].c);
}
}
}
int mx = 0;
for(int i = 0; i <= min(sum / 2, sumA); i++) if(f[sum/2-i]){
for(int a = 0; a < 2; a++)
for(int b = 0; b < 2; b++) mx = max(mx, dp[n&1][i][a][b]);
}
cout << mx + ans;
return 0;
}