题意:有两个人在玩"Gems Fight!"……有B个包,有G种颜色的宝石,这两个人轮流选择某一个包,把这个包里的宝石放到一个共享的熔炉里,当这个熔炉里某一种颜色的宝石大于等于S,那么就可以产生一个魔法石,这个人得到这个魔法石并且还能得到一个额外的回合,两个人都用最佳策略,问最后两个人能获得的魔法石的差是多少。
思路:看到包不多,只有21个,那么可以考虑状压,用一个二进制数表示已经选择的包,则dp[state]表示在state的状态下,先手所能获得的最大值。由于每个状态下的宝石剩余的数量是可以确定的(因为到S就会被去掉),所以可以预处理一下每个状态下剩余的每个颜色的宝石的数量,接下来在当前状态下选择某一个包,看是否能产生出魔法石,如果能产生,那么他还可以在进行一个回合,所以dp[state]=max(dp[state],pt+f(state|(1<<i))) (pt是当前产生魔法石的数量),如果不能,则下一回合是对方选择,所以dp[state]=max(dp[state],pt-f(st|(1<<i)))。
代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<map>
#include<queue>
#include<stack>
#include<cmath>
#include<vector>
#define inf 0x3f3f3f3f
#define Inf 0x3FFFFFFFFFFFFFFFLL
#define eps 1e-9
#define pi acos(-1.0)
using namespace std;
typedef long long ll;
const int maxn=1<<21;
int color[10],bag[25][15],G,B,S;
int dp[maxn],remain[maxn][10];
bool vis[maxn];
int f(int st)
{
if(vis[st]) return dp[st];
if(st==((1<<B)-1))
{
return 0;
}
vis[st]=true;
int &res=dp[st];
res=inf;
for(int i=0;i<B;++i)
{
if(!(st&(1<<i)))
{
int pt=0;
for(int j=1;j<=bag[i][0];++j)
remain[st][bag[i][j]]++;
for(int j=0;j<=G;++j)
pt+=remain[st][j]/S;
for(int j=1;j<=bag[i][0];++j)
remain[st][bag[i][j]]--;
if(pt)
{
if(res==inf) res=pt+f(st|(1<<i));
else res=max(res,pt+f(st|(1<<i)));
}
else
{
if(res==inf) res=pt-f(st|(1<<i));
else res=max(res,pt-f(st|(1<<i)));
}
}
}
return res;
}
int main()
{
//freopen("a.txt","r",stdin);
//freopen("out.txt","w",stdout);
while(~scanf("%d%d%d",&G,&B,&S))
{
if(G==0&&B==0&&S==0) break;
for(int i=0;i<B;++i)
{
scanf("%d",&bag[i][0]);
for(int j=1;j<=bag[i][0];++j)
scanf("%d",&bag[i][j]);
}
memset(remain,0,sizeof(remain));
int total=1<<B,tmp;
for(int i=0;i<total;++i)
{
tmp=i;
int j=0;
while(tmp)
{
if(tmp&1)
{
for(int k=1;k<=bag[j][0];++k)
remain[i][bag[j][k]]++;
}
tmp>>=1;
j++;
}
for(j=0;j<=G;++j)
remain[i][j]%=S;
}
memset(vis,0,sizeof(vis));
int ans=f(0);
printf("%d\n",ans);
}
return 0;
}