HOJ 2177 取(2堆)石子游戏 (威佐夫博弈)

首先要明白威佐夫博弈的原理:威佐夫博弈百度百科讲得十分的详细了,还不会的可以先看懂原理。

给出一个局势(a,b),其中a<=b。我们要判断它是不是奇异局势,如果是的话就已经输了。如果不是奇异局势的话,我们可以通过适当的方法将它转换成奇异局势,这样就胜券在握了。

把非奇异局势转换成奇异局势是本题的关键:(a和b表示给出的数据(a, b),(ak, bk)表示定义中的公式)

  • 先做一点铺垫,也可以先看下面的情况分类。如果暴力每次都让k从0开始取,必定是浪费效率的,那么每一次数据中的k要从哪里开始取是一个问题,答案是:从定义出发。

  • 奇异局势(ak,ak + k)中,ak = [k * (1 + √5)/ 2],中括号代表向下取整。我们可以利用[x] <= x这个性质。ak = [k * (1 + √5)/ 2] <= k * (1 + √5)/ 2,可以得到 k >= 2 * ak / (1 + √5)。结合题目这样就得到了一个下限2 * a / (1 + √5)。

  • 但是,还不够。(如果先看这里的解释,看到这里建议先把下面的看了再回来看这一点,便于理解)这个下限没有考虑一种情况,就是下面讲到的最后一种情况。所以我们要稍作修改,令ak = [k * (1 + √5)/ 2] + k,继续进行和上面相同的转换,由ak <= k * (1 + √5)/ 2 + k得到 k >= 2 * ak / (3 + √5),这就是最终的下限, k从 k >= 2 * a / (3+ √5)开始进行各种情况的判断,直到 ak>a 且 ak + k > b结束判断。

  • 分四种情况进行转换,第一种是两堆中取相等,使得取后成为一个奇异局势。

  • 第二种是a = ak,b > ak + k,这样直接对b取到剩下ak + k个就行了。这里包括了 a == b,拿完成为奇异局势(0,0)。

  • 第三种情况,b = ak + k,a > ak,把a取到剩下ak。

  • 第四种情况,a = ak + k。直接看有点难理解,HOJ 2177 取(2堆)石子游戏的测试数据的答案3 5就是这种情况,乍一看有点迷惑,其实不难。这种情况把b取到剩下ak就可以了。

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#pragma warning(disable: 4996)
using namespace std;

int swap(int x, int y) {
	if (x <= y) return 1;
	int temp = x;
	x = y;
	y = temp;
	return 1;
}

int main() {
	int a, b, k, flag, ansx, ansy, can;
	while (scanf("%d %d", &a, &b), a, b) {
		swap(a, b);
		can = -1;
		flag = 0;
		k = int(2 * a / (3 + sqrt(5)));
		if (a == b) {//a==b的情况,先输出0 0
			can = 1;
			printf("1\n0 0\n");
		}
		while (1) {
			int X = int(k * (1 + sqrt(5)) / 2);
			if (a == X && b == a + k) {//面对奇异局势必输的情况
				can = 0;
				printf("0\n");
				break;
			}
			if (b - a == k && a > X) {//第一种情况
				if (can == -1) 
					printf("1\n");
				can = 1;
				if(X != 0 || X + k != 0)
					printf("%d %d\n", X, X + k);
			}
			if (a == X && b > a + k) {//第二种情况
				flag = 1;
				ansx = a;
				ansy = a + k;
			}
			if (b == X + k && a > X) {//第三种情况
				flag = 1;
				ansx = b - k;
				ansy = b;
			}
			if (a == X + k) {//第四种情况
				flag = 1;
				ansx = X;
				ansy = a;
			}
			if (X > a && X + k > b)
				break;
			k++;
		}//while(1)
		if (flag) {
			if (can == -1)
				printf("1\n");
			printf("%d %d\n", ansx, ansy);
		}
	}//while
	return 0;
}

PS: 菜鸡一个,这篇可能有些地方表达的不是很好,也可能会有写错的理解错的,欢迎指出讨论。

猜你喜欢

转载自blog.csdn.net/qq_44724908/article/details/104314529
今日推荐