分析:博弈的过程是一个树,而且要知道在最优策略情况下,肯定是选择了树上的某一条树枝,也就是一条路径。
所以我们可以用dp来找出某一条最优路径,而且我们可以发现2个人博弈的目的是为了让自己与另一个人的差值尽可能的大。
解法:用dp[i]表示状态i(能拿的置为1)的情况下先手减去后手分数的最大值。
初始化dp[(1<<n)-1] = 0; 我们可以预处理出sum[i]表示状态i的情况下有能获得的总共的分数。
对于dp[i], 选第j个包, 如果这次拿好分数增加tp, 那么用 dp[i]+tp 去转移 dp[i^(1<<j)],
否则先后手交换, 用-dp[i]去更新dp[i^(1<<j)]。
杭电rank1 2100ms, 后来对预换了一种方法预处理sum数组, 845ms
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int G, B, S;
const int maxn = 1<<21;
int cnt[22];
int a[22][11];
int dp[maxn];
int sum[maxn];
int tp[9];
int two[22];
int res[maxn][9];
int Log[maxn];
int main() {
int i, j;
two[0] = 1;
for(i = 1; i <= 21; i++)
two[i] = two[i-1] << 1;
Log[0] = -1;
for(i = 1; i < two[21]; i++)
Log[i] = Log[i>>1] + 1;
while(~scanf("%d%d%d", &G, &B, &S)) {
if(!G && !B && !S) break;
for(i = 0; i < B; i++) {
scanf("%d", &cnt[i]);
memset(a[i], 0, sizeof(a[i]));
for(j = 0; j < cnt[i]; j++) {
int x;
scanf("%d", &x);
a[i][x]++;
}
}
memset(res[0], 0, sizeof(res[0]));
sum[0] = 0;
for(i = 1; i < two[B]; i++) {
int pre = i^(i&-i);
sum[i] = sum[pre];
int cur = Log[i&-i];
for(j = 1; j <= G; j++)
res[i][j] = res[pre][j] + a[cur][j];
}
for(i = 1; i <two[B]; i++) {
sum[i] = 0;
for(j = 1; j <= G; j++)
sum[i] += res[i][j]/S;
}
for(i = 0; i < two[B]; i++) dp[i] = -1e9;
dp[two[B]-1] = 0;
for(i = two[B]-1; i >= 0; i--) {
for(j = i; j; j -= j&-j) {
int pre = i^(j&-j);
int tp = sum[i] -sum[pre];
if(tp > 0)
dp[pre] = max(dp[pre], dp[i]+tp);
else
dp[pre] = max(dp[pre], -dp[i]);
}
}
printf("%d\n", dp[0]);
}
return 0;
}