2011 ACM-ICPC 成都赛区A题 Alice and Bob (博弈动规)

题目大意:

       有K堆石子,每堆有Ki个,两人的操作可以是:
            1 从某一堆拿走一个 如果该堆在此之后没有石子了,就消失
            2 合并两个堆
       求是否先手必胜,先手胜输出Alice,否则输出Bob


思路:

       这道题读完后毫无头绪,推了半天也推不个所以然来,参看大神代码后,感觉就是一个记忆化搜索啊,唉,知识学多了不会用还是白搭,还得多做题啊!

       这里我们把数字分成 1,2,大于等于3的奇数,大于等于4的偶数四类。
       这样分的原因在于,一个大于3的奇数是实际上等价于3的;因为每当对手减一个,自己也减一个,就又变回了一个大于3的奇数,最终变成3。同理,所有大于2的偶数等价于4。
       所以我们用dp[a][b][c][d]表示有a个1,b个2,c个3,d个4是不是一个必胜态,然后动规求解就好了。


代码:

#include 
#define N 51

bool dp[N][N][N][N] = {0};
bool vis[N][N][N][N] = {0};

int F(int a, int b, int c, int d)							// 一个类似记忆化搜索的过程
{
	if(!vis[a][b][c][d]){									// 如果后继状态为P状态,则此状态为N状态
		if(a >= 1 && !F(a - 1, b, c, d)) dp[a][b][c][d] = 1;			// 从 a 中某堆拿走一个,消失
		if(b >= 1 && !F(a + 1, b - 1, c, d)) dp[a][b][c][d] = 1;		// 从 b 中某堆拿走一个,变成 a
		if(c >= 1 && !F(a, b + 1, c - 1, d)) dp[a][b][c][d] = 1;		// 从 c 中某堆拿走一个,变成 b
		if(d >= 1 && !F(a, b, c + 1, d - 1)) dp[a][b][c][d] = 1;		// 从 d 中某堆拿走一个,变成 c

		if(a >= 2 && !F(a - 2, b + 1, c, d)) dp[a][b][c][d] = 1;		// 合并 a 中的两堆,变成 b 类的一堆
		if(b >= 2 && !F(a, b - 2, c, d + 1)) dp[a][b][c][d] = 1;		// 合并 b 中的两堆,变成 d 类的一堆
		if(c >= 2 && !F(a, b, c - 2, d + 1)) dp[a][b][c][d] = 1;		// 合并 c 中的两堆,变成 d 类的一堆
		if(d >= 2 && !F(a, b, c, d - 2 + 1)) dp[a][b][c][d] = 1;		// 合并 d 中的两堆,变成 d 类的一堆

		if(a >= 1 && b >= 1 && !F(a - 1, b - 1, c + 1, d)) dp[a][b][c][d] = 1;		// 合并 a、b 中的一堆,变成 c 类的一堆
		if(a >= 1 && c >= 1 && !F(a - 1, b, c - 1, d + 1)) dp[a][b][c][d] = 1;		// 合并 a、c 中的一堆,变成 d 类的一堆
		if(a >= 1 && d >= 1 && !F(a - 1, b, c + 1, d - 1)) dp[a][b][c][d] = 1;		// 合并 a、d 中的一堆,变成 c 类的一堆
		if(b >= 1 && c >= 1 && !F(a, b - 1, c - 1 + 1, d)) dp[a][b][c][d] = 1;		// 合并 b、c 中的一堆,变成 c 类的一堆
		if(b >= 1 && d >= 1 && !F(a, b - 2, c, d - 1 + 1)) dp[a][b][c][d] = 1;		// 合并 b、d 中的一堆,变成 d 类的一堆
		if(c >= 1 && d >= 1 && !F(a, b, c - 1 + 1, d - 1)) dp[a][b][c][d] = 1;		// 合并 c、d 中的一堆,变成 c 类的一堆
	
		vis[a][b][c][d] = 1;
	}
	
	return dp[a][b][c][d];
}

int main()
{
	int loop, n, t, ct = 1;
	int a, b, c, d, ans;			// a 表示只有一个石子的堆数,b 表示有只有两个石子的堆数,c 表示有奇数个石子的堆数, d 表示有偶数个石子的堆数
	scanf("%d", &loop);
	while(ct <= loop){
		scanf("%d", &n);
		a = b = c = d = 0;
		for(int i = 0; i < n; i ++){
			scanf("%d", &t);
			if(t == 1) a ++;
			else if(t == 2)	b ++;
			else if(t % 2 == 1) c ++;
			else d ++;
		}

		ans = F(a, b, c, d);
		printf("Case #%d: ", ct ++);
		if(ans)
			printf("Alice\n");
		else
			printf("Bob\n");
	}

	return 0;
}


你可能感兴趣的:(博弈)