HDU 4778 Gems Fight!(博弈+状压)

Problem Description
  Alice and Bob are playing "Gems Fight!":
  There are Gems of G different colors , packed in B bags. Each bag has several Gems. G different colors are numbered from color 1 to color G.
  Alice and Bob take turns to pick one bag and collect all the Gems inside. A bag cannot be picked twice. The Gems collected are stored in a shared cooker.
  After a player ,we name it as X, put Gems into 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 more than one Magic Stone may be produced, until no S Gems of the same color remained in that cooker. Then X owns those new Magic Stones. When X gets one or more new Magic Stones, he/she will also get a bonus turn. If X gets Magic Stone in a bonus turn, he will get another bonus turn. In short,a player may get multiple 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 as possible.
  Now Alice gets the first turn, and she wants to know, if  both of them act the optimal way, what will be the difference between the number of her Magic Stones and the number of Bob's Magic Stones at the end of the game.
 

Input
  There are several cases(<=20).
  In each case, there are three integers at the first line: G, B, and S. Their meanings are mentioned above.
  Then B lines follow. Each line describes a bag in the following format:
  
  n c 1 c 2 ... c n
  
  It means that there are n Gems in the bag and their colors are color c 1,color c 2...and color c n 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's Magic 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
Hint
  For the first case, in turn 2, bob has to choose at least one bag, so that Alice will make a Magic Stone at the end of turn 3, thus get turn 4 and get all the three Magic Stones.
 

Source
 
题意:
有G种颜色的宝石,有B个袋子,每个袋子里有各种颜色的宝石若干,有一个cooker 
两个人轮流选一个袋子,将袋子里面的宝石全部丢到cooker里面,如果cooker里面某种颜色的宝石达到数量S就会melt成魔石
而这个操作的人就会得到魔石,而得到魔石的人可以额外获得一次选袋子的机会
现在给出G B S和每个袋子里面宝石的情况,求当2个人都选择最优的策略的时候,先手的最大收益
这里的收益指先手获得的魔石数减去后手的魔石数,所以这个收益可能是负的
分析:
首先明确一点,这题是博弈!博弈!博弈!就因为那一句“双方选取最优策略”
博弈的显著特点就是双方选择最优策略,就因为这一点,在游戏过程中,
真正重要的是你 面对的局面,至于你是如何走到该局面的并不在考虑范围内

对于博弈,我们关心的是在当前局面做出最优的选择,但是最优的选择是如何做出的呢?

往往是 从终态去逆推当前的状态

先说清楚,dp[state]记录的是state这个局面能给面对此局面的人带来的最大收益

局面记录的是袋子是否被拿走(用0表示袋子被拿走,1表示袋子没有被拿走)

现在假设有4个袋子,它的终态就是4个袋子都被拿完了

即 :  0 0 0 0                                 

那么我们可以求出局面  0 0 0 1、0 0 1 0、0 1 0 0、1 0 0 0的收益 ,怎么求?很简单不说了

主要说说如何求 1 1 0 0这个局面的收益

这里有2个可以拿的袋子,假设我们从中拿走第1个袋子(记为 j )

那么这个j号袋子里的石头会被丢到cooker里面,这时候会有2种情况:

① 假设丢到cooker里面之后能得到魔石get个

那么这个局面能带给面对此局面的人的收益是 : get + dp[0100]

看懂了吗,当此人获得get个魔石后,他就会额外获得一次机会,故而【0100】这个局面也是由他面对,故而加上【0100】这个局面的最大收益

② 假设袋子j里面的宝石丢到cooker里面没有获得魔石

那么这个局面的收益是 0 - dp[0100]

因为【0100】这个局面就由对手面对,对手得到的dp[0100]相对面对该局面的人而言就是损失dp[0100]

而最优的选择,就是所有可能的收益中最大的

这个例子在具体代码中体现为

if (get > 0) dp[i] = max(dp[i] ,get + dp[i^(1<<j)]);

else dp[i] = max(dp[i],0 - dp[i^(1<<j)] )

其中i^(1<<j)就是i的子局面,这里的异或将i这个状态中的第j个1化为了0,当然,这个袋子能拿的前提是没有被拿走所以前面还有if (i&(1<<j))的判断

然后还需要保存的是cooker里面的状态,这个就很简单了,直接看代码:

#define mem(a,x) memset(a,x,sizeof(a))
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
typedef long long ll;
const int inf = 1<<29;
int c[22][20];//c[i][j]表示第i个袋子里面颜色为j的宝石的数量
int cooker[20];//保存当前状态下cooker里面各颜色宝石的数量
//cooker[i]表示i颜色的宝石在cooker里面的个数(melt了的不算,仅代表当前状态)
int tmp[20];//tmp[i]表示当前状态下选择一个袋子拿走之后i颜色在cooker里面的个数
int dp[1<<22];//dp[i]表示当先手面对状态i的时候,先手能从该局面获得的最大收益
//这里的收益是指先手减去后手的分数 可能为负 
/*
	对每个袋子进行状态压缩:
	0表示该处的袋子已经被拿走了
	1表示该处的袋子还在
	
	博弈一个很显著的特点,真正对你有意义的是你面对的局面
	而你是如何到达此局面的并不重要
	0表示的袋子已经被拿走了,是谁拿走的,是何时拿走的都不重要
	你只需要在你当前的局面做出最佳的选择 
*/ 
int main()
{
    int G,B,S;
    while (scanf("%d%d%d",&G,&B,&S) == 3)
    {
    	if (G==0&&B==0&&S==0) break;
    	mem(c,0);
    	for (int i = 0;i < B;++i)
		{
			int n;scanf("%d",&n);
			for (int j = 0,x;j < n;++j)
			{
				scanf("%d",&x);
				c[i][x]++;
			}
		} 
		//开始状态转移
		dp[0] = 0;// 袋子全部被拿走的状态先手的收益为 0
		int V = (1<<B)-1;//二进制位全部为 1
		for (int i = 1;i <= V;++i)
		{
			dp[i] = -inf;
			mem(cooker,0);
			//得到当前局面cooker里面的颜色的情况 
			for (int j = 0;j < B;++j)
			{
				if ((i&(1<<j))==0) //0表示此处的袋子已经被拿走了,放到了cooker里面
				{
					for (int k = 1;k <= G;++k)
					{
						cooker[k] += c[j][k];
						while (cooker[k]>=S) cooker[k] -= S;//melt 
					}
				} 
			}
			//现在开始做出当前局面的最优选择
			for (int j = 0;j < B;++j)
			{
				if (i&(1<<j)) //j是一个没有被拿走的袋子
				{
					for (int k = 1;k <= G;++k) tmp[k] = cooker[k];
					int get = 0;//用来保存拿j袋子的收益
					for (int k = 1;k <= G;++k)
					{
						tmp[k] += c[j][k];
						while (tmp[k] >= S)//melt
						{
							tmp[k] -= S;
							get++;	
						}
					} 
					if (get > 0) dp[i] = max(dp[i],get+dp[i^(1<<j)]);//如果有收获,可以拿了后再获得一次机会将i^(1<<j)局面的收益一并拿了
					else dp[i] = max(dp[i],0-dp[i^(1<<j)]);//如果没有收获,那么 i^(1<<j)的局面给对手,对手的收益就是自己的损失 
				} 
			} 
		} 
		printf("%d\n",dp[V]);//先手最初面对的就是全部没有被拿走的局面 
	}
    return 0;
}


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值