测试地址:Gems Fight!
题目大意:有
B(≤21)
包宝石,宝石共有
G(≤8)
种颜色,双方每轮取走一包并放在一个共用的容器里,如果一个玩家取完一包后,该容器内有
S(<20)
个同色宝石,则可以将它们合成为一块魔法石并收入囊中,一直合成直到无法合成为止。另外,如果一轮后该玩家合成了至少一块魔法石,那么他可以获得额外的一轮,如果又合成了魔法石就又获得额外的一轮,以此类推。玩家的目标是获得尽量多的魔法石。现在的问题是,若双方都采用最佳策略,先手获得的魔法石数减去后手获得的魔法石数的数为多少。
做法:本题需要用到状态压缩DP+博弈+记忆化搜索。
这是一道好题啊…通过分析,发现一个状态仅受后面的状态影响,而且
B
很小,于是考虑状态压缩DP。
设
如果能,那么下一轮还是先手取,所以这种情况的状态转移方程为
如果不能,那么下一轮变为后手取,那么这种情况的状态转移方程就是:
f(s)=max(f(s),−f(next))
。
注意
f(s)
最优值可能为负,所以算法一开始应初始化
f(s)
为
−inf
,边界条件为
f(2B−1)=0
。这样我们就解决了这个问题,时间复杂度为
O(BG2B)
,时限比较宽,所以能够通过。
下列代码是用记忆化搜索的写法写的,看着比较清楚易懂。
以下是本人代码:
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#define inf 1000000000
using namespace std;
int g,b,s,n;
int c[25][11],f[3000010];
bool vis[3000010]={0};
int dp(int x)
{
if (vis[x]) return f[x];
if (x==(1<<b)-1) return 0;
int v=x,i=1,cl[10]={0},add=0;
f[x]=-inf;
while(v)
{
if (v&1)
{
for(int j=1;j<=g;j++)
{
cl[j]+=c[i][j];
cl[j]%=s;
}
}
v>>=1,i++;
}
for(i=1;i<=b;i++)
if (!(x&(1<<(i-1))))
{
add=0;
for(int j=1;j<=g;j++)
add+=(cl[j]+c[i][j])/s;
if (add>0) f[x]=max(f[x],dp(x+(1<<(i-1)))+add);
else f[x]=max(f[x],-dp(x+(1<<(i-1))));
}
vis[x]=1;
return f[x];
}
int main()
{
while(scanf("%d%d%d",&g,&b,&s)&&g&&b&&s)
{
memset(c,0,sizeof(c));
memset(vis,0,sizeof(vis));
for(int i=1;i<=b;i++)
{
scanf("%d",&n);
while(n--)
{
int a;
scanf("%d",&a);
c[i][a]++;
}
}
printf("%d\n",dp(0));
}
return 0;
}