这一题特别像大白书Ch1的 ex.28 Sum游戏,臣妾对着这个example+看了一堆题解终于把这道博弈DP想清楚了%>_<%
首先要注意这一题里面总的score是不变的,因为bag都放在同一个cooker里,magic stone要么给A要么给B
用二进制数表示当前状态,1001表示A or B有第一个和第四个bag可以选,第二个和第三个bag之前已经选过了(即不可选)所以对应的score[1001]应该计算把第二个和第三个bag放到cooker可以得到的score,我开始把这个弄反了==算成了第一个和第四个的score==
dp[state]表示当前状态(有哪些bag可选,有哪些bag已经选过了)开始博弈,到最后先手得分的最大值。
状态转移时,假设state对应的是A是先手,如果state&(1<<i)!=0,则说明bag i可选,下一个状态就是newstate=state^(1<<i),newstate其实就是将state里面的i位从1变到0,表示已经选过了bag i。
改变的分数就是val=score[newstate]-score[state]
如果val>0,那么下一步还是A,所以dp[state]=max(dp[state],val+dp[newstate])
如果选bag i无法使得A加分,那么下一步就换成B是先手,所以dp[newstate]就表示B是先手,选到最后,B得分最大值,因为总分数一定的,所以A的分数=state状态下对应的总分数-dp[下一个回合B先出手]
state状态下对应的总分数=score[0]-score[state],假设state=1001,那么现在还可以把bag 1 and bag 4放进去,而score[1001]表示把bag 2 和bag 3 放进cooker的得分,所以要用total score相减
因为一直取max,就可以保证是最优的。
这里面状态转移不会转移到非法的状态,所以记忆化搜索里面也木有非法状态的特判。
最后的分数=dp[(1<<b)-1]-(score[0]-dp[(1<<b)-1]),score[0]表示total score
DP过程中先手后手对应的是A还是B是会变的==
#include<iostream> #include<stdio.h> #include<cstdio> #include<stdlib.h> #include<vector> #include<string> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<queue> #include <ctype.h> #include<map> #include<time.h> using namespace std; //hdu 4778 const int maxn=1<<21; int b; int g; int n; int s; int c[22][12]; int score[maxn]; int sum[20]; int dp[maxn]; int vis[maxn]; void caltotal()//计算cooker中每个状态的score,=0表示已经选过了放进了cooker { for(int m=0;m<(1<<b);m++) { memset(sum,0,sizeof(sum)); for(int i=0;i<b;i++) { if((m&(1<<i))==0) { for(int j=1;j<=g;j++) { sum[j]+=c[i][j]; } } } for(int i=1;i<=g;i++) { score[m]+=sum[i]/s; } } } int dfs(int state) { if(vis[state]) { return dp[state]; } vis[state]=1; int ret=0; for(int i=0;i<b;i++) { if(state&(1<<i))//=1表示当前状态已经存在bag i,所以i可选。向选过bag i的状态转移(所以newstate的i位是0) { int newstate=state^(1<<i); int val=score[newstate]-score[state]; if(val>0) { ret=max(ret,val+dfs(newstate)); } else { ret=max(ret,(score[0]-score[state])-dfs(newstate)); //所有bag都放到cooker中的score-当前状态还有某些包可选对应的score(只放了一部分bag到cooker里的score)=s当前状态的总分数 } } } return dp[state]=ret;//一定要赋值才能实现记忆化搜索! } int main() { freopen("input.txt","r",stdin); //freopen("data.txt","r",stdin); //freopen("out1.txt","w",stdout); while(true) //while(scanf("%d %d %d",&g,&b,&s),g|b|s) { scanf("%d %d %d",&g,&b,&s); if(!b&&!g&&!s) break; memset(score,0,sizeof(score)); memset(c,0,sizeof(c)); memset(dp,0,sizeof(dp)); memset(vis,0,sizeof(vis)); for(int i=0;i<b;i++) { scanf("%d",&n); for(int j=0;j<n;j++) { int color=0; scanf("%d",&color); c[i][color]++; } } caltotal(); int ans=dfs((1<<b)-1)*2-score[0];//0表示已经选过放在cooker中了,所以score[0]是总的分数 //dp[11...11]-(total score-dp[11...11]) printf("%d\n",ans); } return 0; }