【做題】CSA49F - Card Collecting Game——思維&dp
原文連結 https://www.cnblogs.com/cly-none/p/CSA49F.html
題意:Alice和Bob在玩遊戲。有\(n\)種卡牌,每種卡牌有\(b_i\)張,保證\(\sum b_i\)為偶數。現在,Alice要把所有卡牌任意平分為2份(僅要求每份卡牌數為\(\frac {\sum b_i} {2}\)),並對每份分別進行一次遊戲。第一次遊戲由Alice先手,第二次由Bob先手。
每次遊戲中,Alice和Bob會輪流取走一張卡牌直到取盡。設最後Alice有\(n_i\)張第\(i\)種牌,那麼她會得到\(\left\lfloor \frac {n_i} {a_i} \right\rfloor c_i\)
的分數。一次遊戲的得分是Alice從每種牌得到的分數總和。現在,Alice想要最大化兩次遊戲的得分總和,Bob則想最小化。求出在兩人都採取最優決策時的得分總和。
\(n \leq 2\times 10^3, \ \sum a_i \leq 2 \times 10^3, \ \sum b_i \leq 5\times 10^5, \ c_i \geq 0\)
先考慮如何計算一次遊戲的得分。
首先,因為是輪流取,故對於每\(2a_i\)張卡牌\(i\),都能產生\(c_i\)的分數。因此,我們可以先考慮這一部分的貢獻,然後將所有\(n_i\)對\(2a_i\)取模。
接下來,考慮如果\(n_i < 2a_i - 1\)
因此,對於剩下來的卡牌,我們忽略\(n_i < 2a_i - 1\)的,並對剩下的按\(c_i\)從大到小排序。那麼,若Alice先手,就能得到\(\sum_{2 \nmid i} c_i\)的分數;否則就是\(\sum_{2 | i} c_i\)。
那麼,我們就能得到一個dp的做法。以\(c_i\)為關鍵字排序後,設\(dp[i,j,a,b]\)表示當前處理了前\(i\)種卡牌,第一份已經有\(j\)張卡牌,且第一份有\(a\)
但這樣還不足以解決本題。考慮\(\sum a_i\)比較小,故我們要從這個角度來優化dp。
先注意到兩點,一是我們在轉移時,產生的貢獻只和放到第一份的數量對\(2a_i\)取模的值有關;二是dp狀態中最龐大的\(j\),最後只是用來確定第一份的數量等於\(\frac {\sum b_i} {2}\)的。於是我們考慮對\(j\)進行優化,目的是在轉移時,只用列舉放在第一份的數量對\(2a_i\)取模的值。
於是我們把第一份卡牌分為兩個部分,一部分是所有\(n_i\)對\(2a_i\)取模後的結果,則另一部分的卡牌總數就是\(\sum_{i} 2a_ik_i\)的形式。要保證第一份的卡牌總數為一個固定值,我們就要求出\(\sum_{i} 2a_i k_i\)的能表示出哪些數。
先考慮\(k_i\)的取值範圍。設\(n_i \mod 2a_i = r_i\),那麼\(k_i\)就是在\(\left[ 0, \left\lfloor \frac {b_i - r_i} {2a_i} \right\rfloor \right]\)之間的整數。但\(\left\lfloor \frac {b_i - r_i} {2a_i} \right\rfloor\)有兩種取值:在\(r_i \leq b_i \mod 2a_i\)時,為\(\left\lfloor \frac {b_i} {2a_i} \right\rfloor\);否則是\(\left\lfloor \frac {b_i} {2a_i} \right\rfloor - 1\)。於是我們不妨就令\(k_i\)的上界為\(\left\lfloor \frac {b_i} {2a_i} \right\rfloor - 1\),當\(r_i \leq b_i\)的時候,把多出來的那個\(2a_i\)算在第一部分裡就可以了。
剩下就是一個揹包問題。注意到我們可以把\(a_i\)相等的數放在一起計算,而\(a_i\)只有$ \sqrt {\sum a_i}\(種取值,因此這個問題的複雜度是\)O(\sqrt {\sum a_i} (\sum b_i))$的。
總結一下,第一部分的處理和\(O((\sum b_i)^2)\)的演算法差不多,但這一部分的複雜度是\(O((\sum a_i)^2)\)的。第二部分的揹包,複雜度為\(O(\sqrt {\sum a_i} (\sum b_i))\)。
於是時間複雜度為\(O((\sum a_i)^2 + \sqrt {\sum a_i} (\sum b_i))\)。
#include <bits/stdc++.h>
using namespace std;
const int A = 2010, B = 500010, N = 2010;
int dp[2][A << 2][2][2], bag[B], n, num[A], p, sa, sb, ans;
struct data {
int a,b,c;
bool operator < (const data& x) const {
return c > x.c;
}
} dat[N];
inline void ckmx(int& x,int y) {
x = x < y ? y : x;
}
int main() {
scanf("%d",&n);
for (int i = 1 ; i <= n ; ++ i)
scanf("%d%d%d",&dat[i].a, &dat[i].b, &dat[i].c);
sort(dat+1,dat+n+1);
for (int i = 1 ; i <= n ; ++ i) {
if (dat[i].b / (dat[i].a << 1) - 1 > 0)
num[dat[i].a] += dat[i].b / (dat[i].a << 1) - 1;
sa += dat[i].a;
sb += dat[i].b;
}
p = 1;
memset(dp,-1,sizeof dp);
dp[0][0][0][0] = 0;
for (int i = 1, sum = 0 ; i <= n ; ++ i, p ^= 1) {
memset(dp[p],-1,sizeof dp[p]);
for (int j = 0 ; j <= (sum << 2) ; ++ j)
for (int a = 0 ; a < 2 ; ++ a)
for (int b = 0 ; b < 2 ; ++ b) {
if (dp[p^1][j][a][b] == -1) continue;
for (int k = 0 ; k < 2 * dat[i].a && k <= dat[i].b ; ++ k) {
int tmp = (dat[i].b - k) / (dat[i].a << 1), na = a, nb = b;
if (k == 2 * dat[i].a - 1) {
if (!na) tmp ++;
na ^= 1;
}
if ((dat[i].b - k) % (dat[i].a << 1) == (dat[i].a << 1) - 1) {
if (nb) tmp ++;
nb ^= 1;
}
tmp = tmp * dat[i].c;
ckmx(dp[p][j + k][na][nb], dp[p^1][j][a][b] + tmp);
if (dat[i].b >= 2 * dat[i].a && k <= dat[i].b % (dat[i].a << 1))
ckmx(dp[p][j + k + 2 * dat[i].a][na][nb], dp[p^1][j][a][b] + tmp);
}
}
sum += dat[i].a;
}
bag[0] = 1;
for (int i = 1 ; i < A ; ++ i) {
if (!num[i]) continue;
for (int j = 0 ; j <= sb ; ++ j) {
if (bag[j]) bag[j] = 0;
else {
bag[j] = -1;
if (j >= 2 * i) {
if (bag[j - 2 * i] != -1)
bag[j] = bag[j - 2 * i] + 1;
}
}
}
for (int j = 0 ; j <= sb ; ++ j)
if (bag[j] == -1) bag[j] = 0;
else if (bag[j] <= num[i]) bag[j] = 1;
else bag[j] = 0;
}
p ^= 1;
for (int i = 0 ; i <= (sa << 2) && i <= (sb >> 1) ; ++ i)
for (int a = 0 ; a < 2 ; ++ a)
for (int b = 0 ; b < 2 ; ++ b) {
if (dp[p][i][a][b] == -1) continue;
if (bag[(sb >> 1) - i]) ans = max(ans, dp[p][i][a][b]);
}
printf("%d\n",ans);
return 0;
}
小結:這個問題相當有難度。得到\(O((\sum b_i)^2)\)的dp已經偏難,而後一部分的優化對思維能力和細節處理能力的要求,是在博主目前能力之上的,也體現了演算法優化的一些重要思路。