『博弈·枚举优化』取石子

题目描述

在这里插入图片描述

题解

先考虑只有两堆石子的特性。

根据一个叫做威佐夫博弈的东西,我们发现必败态可以表示为: ( 1 , 2 ) , ( 3 , 5 ) , ( 4 , 7 ) , ( 6 , 10 ) . . . . . . (1,2),(3,5),(4,7),(6,10)...... (1,2),(3,5),(4,7),(6,10)......

由此,我们可以从中找到两个规律:

  • 每一个自然数只能出现一次。
  • 每一对必败态的两数之差都不一样。

我们可以分析一下为什么会出现这样的情况:

  • 假设有 ( x , y ) (x,y) (x,y) ( x , y ′ ) (x,y') (x,y)都是必败态,那么一定可以从y或y’取一部分石子来得到对方,两者可以互相转移。
  • 假设有 ( x , y ) (x,y) (x,y) ( x + k , y + k ) (x+k,y+k) (x+k,y+k),则后者可以取出k个石子转移到了前者。
  • 也就是说如果存在这两种情况,两个必败态是可以相互转移的。然而必败态是不能相互转移的。所以这样的情况是不可能存在的。同时也验证了上述两种规律的正确性。

思考一下如何拓展到3堆石子,显然根据必败态不能相互转移这一个结论:

  • 存在必败态 ( x , y , z ) (x,y,z) (x,y,z),必然不存在必败态 ( x , y , z ′ ) (x,y,z') (x,y,z)其中 z = ̸ z ′ z=\not z' z≠z.我们可以用 f [ x ] [ y ] f[x][y] f[x][y]来表示这个 z z z
  • 我们可以考虑如何求解 f [ x ] [ y ] . f[x][y]. f[x][y].

我们可以枚举这个z,求解有多少个 f [ x ] [ y ] = z f[x][y]=z f[x][y]=z.我们可以类比威佐夫博弈来发现一些性质:

  • 如果 f [ x ] [ y ] = k &lt; z f[x][y]=k&lt;z f[x][y]=k<z,则 ( x + z − k , y + z − k , z ) (x+z-k,y+z-k,z) (x+zk,y+zk,z)一定不可能是必败态,因此可以同时取走 z − k z-k zk个石子来保证相互转移。同时, ( x + z − k , y ) (x+z-k,y) (x+zk,y) ( x , y + z − k ) (x,y+z-k) (x,y+zk)也意味着不存在必败态。
  • 如果显然,对于每一个 z z z来说,不可能出现两组必败态 ( x 1 , y 1 , z ) (x_1,y_1,z) (x1,y1,z) ( x 2 , y 2 , z ) (x_2,y_2,z) (x2,y2,z)满足 ∣ x 1 − y 1 ∣ = ∣ x 2 − y 2 ∣ |x_1-y_1|=|x_2-y_2| x1y1=x2y2,来避免同时取两堆石子进行转移。
  • 显然不能存在两组必败态 ( x 1 , y 1 , z ) (x_1,y_1,z) (x1,y1,z) ( x 2 , y 2 , z ) (x_2,y_2,z) (x2,y2,z)满足 x 1 = x 2 x_1=x_2 x1=x2 y 1 = y 2 y_1=y_2 y1=y2.

然后就可以用一堆数组标记就可以啦~
代码如下:

#include <bits/stdc++.h>

using namespace std;
const int N = 310;

int f[N][N], cnt[N], dlt[N], vis[N*2][N*2];

void work(void)
{
	memset(f,30,sizeof f);
	f[0][0] = 0;
	for (int i=0;i<=300;++i)
	{
		memset(dlt,0,sizeof dlt);//对两种石子同时取得情况进行判重(两数之差)
		memset(cnt,0,sizeof cnt);//对每次取一堆石子的情况进行判重 
		memset(vis,0,sizeof vis);//对三堆石子同时取得情况进行判重
		for (int j=0;j<=300;++j)
		    for (int k=0;k<=j;++k)
			{
				if (f[j][k] < i) {
					int val = i - f[j][k];
					vis[j][k+val] = vis[j+val][k] = vis[j+val][k+val] = 
					vis[k][j+val] = vis[k+val][j] = 1;
				}
				else if (!cnt[j] && !cnt[k] && !vis[j][k] && !vis[k][j] && !dlt[abs(j-k)])
				    f[j][k] = f[k][j] = i, cnt[j] = cnt[k] = vis[j][k] = vis[k][j] = dlt[abs(j-k)] = 1;
			} 
	}
	return;
}

int main(void)
{
	freopen("input.in","r",stdin);
	freopen("output.out","w",stdout);
	work();
	int T; cin>>T;
	while (T --)
	{
		int a,b,c; scanf("%d %d %d", &a, &b, &c);
		f[a][b] == c ? puts("No") : puts("Yes");
	}	
	return 0;
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值