【HDU4778】Gems Fight!-状态压缩DP+博弈

测试地址:Gems Fight!
题目大意:B(21)包宝石,宝石共有G(8)种颜色,双方每轮取走一包并放在一个共用的容器里,如果一个玩家取完一包后,该容器内有S(<20)个同色宝石,则可以将它们合成为一块魔法石并收入囊中,一直合成直到无法合成为止。另外,如果一轮后该玩家合成了至少一块魔法石,那么他可以获得额外的一轮,如果又合成了魔法石就又获得额外的一轮,以此类推。玩家的目标是获得尽量多的魔法石。现在的问题是,若双方都采用最佳策略,先手获得的魔法石数减去后手获得的魔法石数的数为多少。
做法:本题需要用到状态压缩DP+博弈+记忆化搜索。
这是一道好题啊…通过分析,发现一个状态仅受后面的状态影响,而且B很小,于是考虑状态压缩DP。
f(s)为包被取的状态为s的情况下,先手获得的魔法石数减去后手获得的魔法石数的最大值,那么枚举每一个s中为0的位,查看选走该包后能不能合成新的魔法石。
如果能,那么下一轮还是先手取,所以这种情况的状态转移方程为f(s)=max(f(s),f(next)+add),其中next为选走该包后的状态,add为新增的魔法石数。
如果不能,那么下一轮变为后手取,那么这种情况的状态转移方程就是:f(s)=max(f(s),f(next))
注意f(s)最优值可能为负,所以算法一开始应初始化f(s)inf,边界条件为f(2B1)=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
",dp(0));
    }

    return 0;
}
原文地址:https://www.cnblogs.com/Maxwei-wzj/p/9793583.html