题意:
有不超过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;
}