题意:
有不超过21个袋子,每个袋子里有不超过10个球,所有球的颜色总共不超过8种,每次alice和bob轮流拿一个袋子,将其中的球放到外面来;外面每S个同一种颜色的球会变成一块魔法石,并且带来加分,得到加分的人可以继续再拿一次,直到外面不再产生加分。问当alice先手,每个人都用最优的走法,alice的魔法石减去比bob的魔法石等于多少。
思路:
第一次敲这种剪枝和博弈,一开始想直接最大值最小和最小值最大,再加上预测剪枝,没有注意到题意给出的21个袋子其实意味着可以记忆化(1<<21)个状态,T了很多发;后来注意到1<<21可以作为记忆化,而直接记忆化用dp[i][j],表示状态i时,二进制为1的位表示已经拿过了,轮到j拿,0表示alice拿,1表示bob拿,可以使接下来alice-bob为多少分,那么答案就是dp[0][0],没有加预测,本地跑一组极限数据要20秒,但是状态写的感觉好乱,又说不上为什么;后来看了vj里别人的状压,也是最大值最小和最小值最大的思路,只要dp[i]表示状态为i时,当前轮到的这个人拿可以拿多少魔法石,i的二进制为1的位表示那个袋子没被拿过,那么答案就是dp[0]-(tot-dp[0]),tot就是总共可以产生多少块魔法石,而预测剪枝时只要dfs(st,sum),st是状态,sum是剩余多少颗魔法石,当st==0 || sum==0 return 0;这样剪一下就可以了。
#include <iostream> #include <cstdio> #include <cstring> using namespace std; int dp[1<<21]; int bag[21][8],G,B,S; int dfs(int st,int left,int cc[]){ int &re = dp[st]; if(re!=-1) return re; if(left==0 || st==0) return re=0; int maxValue=0; for(int i=0;i<B;i++){ if((st>>i)&1){ int tt[8],bonus=0,sum; for(int j=0;j<G;j++){ tt[j] = bag[i][j]+cc[j]; bonus += tt[j]/S; tt[j]%=S; } if(bonus) sum = bonus + dfs(st^(1<<i),left-bonus,tt); else sum = left - dfs(st^(1<<i),left,tt); if(maxValue < sum) maxValue = sum; } } return re = maxValue; } int main(){ // freopen("data.in","r",stdin); while(scanf("%d%d%d",&G,&B,&S)!=EOF){ if(G==0 && B==0 && S==0) break; int cc[8],sum=0; memset(cc,0,sizeof cc); memset(bag,0,sizeof bag); memset(dp,-1,sizeof dp); for(int i=0,n;i<B;i++){ scanf("%d",&n); for(int j=0,ci;j<n;j++){ scanf("%d",&ci); bag[i][ci-1]++; cc[ci-1]++; } } for(int i=0;i<G;i++){ sum += cc[i]/S; cc[i]=0; } int ans = dfs((1<<B)-1,sum,cc); printf("%d\n",ans+ans-sum); } return 0; }