HDU 4778 Gems Fight!

Problem Description

Alice and Bob are playing "GemsFight!":
There are Gems of G different colors , packedin B bags. Each bag has several Gems. G different colors are numbered fromcolor 1 to color G.
Alice and Bob take turns to pick one bag andcollect all the Gems inside. A bag cannot be picked twice. The Gems collectedare stored in a shared cooker.
After a player ,we name it as X, put Gemsinto the cooker, if there are S Gems which are the same color in the cooker,they will be melted into one Magic Stone. This reaction will go on and morethan one Magic Stone may be produced, until no S Gems of the same colorremained in that cooker. Then X owns those new Magic Stones. When X gets one ormore new Magic Stones, he/she will also get a bonus turn. If X gets Magic Stonein a bonus turn, he will get another bonus turn. In short,a player may getmultiple bonus turns continuously.
There will be B turns in total. The goal of"Gems Fight!" is to get as more Magic Stones than the opponent aspossible.
Now Alice gets the first turn, and she wantsto know, if both of them act the optimal way, what will be thedifference between the number of her Magic Stones and the number of Bob's MagicStones at the end of the game.

 

 

Input

There are several cases(<=20).
In each case, there are three integers at thefirst line: G, B, and S. Their meanings are mentioned above.
Then B lines follow. Each line describes abag in the following format:

n c1 c2 ... cn

It means that there are n Gems in the bag andtheir colors are color c1,color c2...and color cn respectively.
0<=B<=21, 0<=G<=8,0<n<=10, S < 20.
There may be extra blank lines between cases.You can get more information from the sample input.
The input ends with G = 0, B = 0 and S = 0.

 

 

Output

One line for each case: the amount of Alice'sMagic stones minus the amount of Bob's Magic Stones.

 

 

Sample Input

3 4 3

2 2 3

2 1 3

2 1 2

3 2 3 1

 

3 2 2

3 2 3 1

3 1 2 3

 

0 0 0

 

 

Sample Output

3

-3

题目大意:有G种颜色的石头,B个口袋,每种颜色的石头到S,就可以得到一个魔法石。接着B个口袋中,每个口袋有n块石头,每个石头的颜色是ci。Alice先手,Bob后手,一人一次轮流拿口袋,每次拿出一个袋子里面的全部石头。两人拿出来放在一起。一旦某种颜色的石头达到S了,就这个人就得到一块魔法石,并且获得额外的一次机会,可以继续拿。问最后Alice得到的魔法石-Bob得到的魔法石最大值是多少。

 

思路就是dp[i]表示的是取包时,还剩包的状态是i,先手能取到的最多魔法石的数量。转移方程就是:每次取了后能得到x个魔法石,

如果,x>0,即是可以继续,那么dp[i] =max(dp[i],dp[i^(1<<j)]+x);

如果,x=0,那么就需要交换先后手,dp[i] =max(dp[i],left[i]-dp[i^(1<<j)]);这里的left[i]是剩下包为i时,还能取到的魔法石的个数。

 

开始直接写DP一直卡着,left[i]一直处理不好。就写了一个递归的。

solve(int x ,int left ,intston[]) ,x表示剩余包的状态,left表示剩余包为x时,还能取到的魔法石的个数,ston表示剩余包为i时,当前各种颜色的石头的剩余值。最后得到的dp[(1<<b)-1]即是先手能拿到的最多的魔法石的个数。这个方法很好像,直接按着思路写就完了。

#include <iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#define INF -999999999
using namespace std;

int g ,b ,s;
int no[30][11] ,gems[11] ,sum;
int dp[1<<21];

int solve(int x ,int left ,int ston[])
{
    if(left==0||x==0)
    {
        return 0;
    }
    if(dp[x]!=INF)
    {
        return dp[x];
    }
    int y ,ss[11] ,re ,ans = 0 ,temp;
    for(int i = b-1;i>=0;i--)
    {
        if((x>>i)&1)
        {
            y = 0;
            re = 0;
            temp = x^(1<<i);
            for(int k = 1;k <= g;k++)
            {
                ss[k] = ston[k] + no[i][k];
                y += (ss[k] / s);
                ss[k] = ss[k] % s;
            }
            if(y > 0)
            {
                re = solve(temp,left - y,ss) + y;
            }
            else
            {
                re = left - solve(temp,left,ss);
            }
            ans = max(ans,re);
        }
    }
    dp[x] = ans;
    return ans;
}

int main()
{
    int re ,cnt[11] ,n ,ans;
    while(scanf("%d%d%d",&g,&b,&s),g+b+s!=0)
    {
        memset(gems,0,sizeof(gems));
        memset(no,0,sizeof(no));
        sum = 0;
        for(int i = 0;i<b;i++)
        {
            scanf("%d",&n);
            for(int j = 0;j < n;j++)
            {
                scanf("%d",&re);
                no[i][re]++;
            }
            for(int j = 1;j<=g;j++)
            {
                gems[j] += no[i][j];
            }
        }
        for(int i = 1;i<=g;i++)
        {
            sum += (gems[i] / s);
        }
        int m = 1<<b ,x;
        memset(cnt,0,sizeof(cnt));
        for(int i = 0;i < m;i++)
        {
            dp[i] = INF;
        }
        ans = solve(m-1,sum,cnt);
        printf("%d\n",2*ans-sum);
    }
    return 0;
}


 

 

过了一天,才把直接dp的方法写出来。要算left[i]就需要计算剩余的石头的数量。这里有一个逆向的过程,有点智商拙计,想了半天。left[0] 显然为0.cnt[0][k]显然是k这种颜色最后的剩余值。通过cnt[i^(1<<j)]的剩余值,和取得j这个包含有各种颜色石头的个数no[j],就可以算出剩余包的状态为i时的cnt[i]。同时,也可以算出,由i到i(1<<j)这个过程能拿到x块魔法石,进而可以通过left[i^(1<<j)]求得left[i]。

 

当cnt[i^(1<<j)][k] >=no[j][k]时,显然说明由i到i^(1<<j)的过程,第k这种颜色的石头不能拿到魔法石,则cnt[i][k] = cnt[i^(1<<j)][k]- no[j][k];如果cnt[i^(1<<j)][k]< no[j][k],则说明由i到i^(1<<j)的过程,k这种颜色的石头能拿到魔法石,而拿到的块数就是re = ((no[j][k] -cnt[i^(1<<j)][k]) + s - 1) / s;i状态时剩余的第k这种颜色的石头为cnt[i][k] = re * s +cnt[i^(1<<j)][k] - no[j][k];(就是这里拙计了。。。。。)故left[i] =left[i^(1<<j)]+x。

剩下的就用那个dp方程就完了

#include <iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
using namespace std;

int g ,b ,s ,n;
int no[30][11] ,gems[11] ,sum;
int dp[1<<21] ,cnt[1<<21][11] ,le[1<<21];

int main()
{
    int re;
    while(scanf("%d%d%d",&g,&b,&s),g+b+s!=0)
    {
        memset(gems,0,sizeof(gems));
        memset(dp,0,sizeof(dp));
        memset(no,0,sizeof(no));
		memset(cnt,0,sizeof(cnt));
        sum = 0;
        for(int i = 0;i<b;i++)
        {
            scanf("%d",&n);
            for(int j = 0;j < n;j++)
            {
                scanf("%d",&re);
                no[i][re]++;
            }
            for(int j = 1;j<=g;j++)
            {
                gems[j] += no[i][j];
            }
        }
		
        for(int i = 1;i<=g;i++)
        {
            sum += (gems[i] / s);
			cnt[0][i] = gems[i] % s;
        }
        int m = 1<<b ,x;
        le[0] = 0;
        for(int i = 0;i < m;i++)
        {
            for(int j = 0;j < b;j++)
            {
                if((i>>j)&1)
                {
                    x = 0;
					//cnt存的是当剩余包为i时,各种石头剩下的值。
					for(int k = 1;k <= g;k++)
					{
						if(cnt[i^(1<<j)][k] >= no[j][k])
						{
							cnt[i][k] = cnt[i^(1<<j)][k] - no[j][k];
						}
						else
						{
							re = ((no[j][k] - cnt[i^(1<<j)][k]) + s - 1) / s;
							x += re;
							cnt[i][k] = re * s + cnt[i^(1<<j)][k] - no[j][k];
						}
					}
                    le[i] = le[i^(1<<j)] + x;
                    if(x > 0)
                    {
                        dp[i] = max(dp[i],dp[i^(1<<j)] + x);
                    }
                    else
                    {
                        dp[i] = max(dp[i] ,le[i] - dp[i^(1<<j)]);
                    }
                }
            }
        }
        printf("%d\n",2 * dp[m-1] - sum);
    }
    return 0;
}


 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值