分析:博弈的过程是一个树,而且要知道在最优策略情况下,肯定是选择了树上的某一条树枝,也就是一条路径。
所以我们可以用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; }