P6270 [SHOI2002]取石子游戏 题解

本文介绍了取石子游戏的博弈论策略,讨论了如何确定先手玩家是否必然获胜。通过动态规划和边界条件设定,揭示了游戏状态的性质,并给出了一种O(n)时间复杂度的解决方案。
摘要由CSDN通过智能技术生成

博客园同步

原题链接

POJ链接

简要题意:

双方轮流拿石子,共两堆石子,每次可以拿去一堆中的任意个或者两堆中的相同多个。先走方是否一定能获胜(失败)?

博弈论经典:威佐夫博弈 模板题。

显然,我们用 P P P 态表示必败态, A A A 态表示必胜态,用 f x , y f_{x,y} fx,y 表示两堆分别为 x x x 个和 y y y 个的状态。显然:

f x , y ∈ ( A , P ) f_{x,y} \in \bigg( A,P \bigg) fx,y(A,P)

然后,先规定边界:

f 0 , 0 = P , f 0 , x = f x , 0 = A f_{0,0} = P , f_{0,x} = f_{x,0} = A f0,0=P,f0,x=fx,0=A

f x , y = f y , x f_{x,y} = f{y,x} fx,y=fy,x

我们只需考虑 x < y x<y x<y 的情况。

去处边界, f 1 , 2 = P f_{1,2} = P f1,2=P,这是显然的,你无论如何都是输。

然后, f 1 , k = A ( k > 2 ) f_{1,k} = A(k >2) f1,k=A(k>2),这是因为,你只要把 k k k 个石子降为 2 2 2 个石子,然后对方是先手, ∵ f 1 , 2 = P \because f_{1,2} = P f1,2=P ∴ f 1 , k = A ( k > 2 ) \therefore f_{1,k} = A(k > 2) f1,k=A(k>2).其中式子中可以认为 P = 0 , A = 1 P=0,A=1 P=0,A=1.

同理, f 2 , k = A ( k > 1 ) f_{2,k} = A(k>1) f2,k=A(k>1),因为 f 2 , 1 = P f_{2,1} = P f2,1=P,把 k k k 堆石子弄成一堆就行。

从上面可以看出,如果把 f x , y f_{x,y} fx,y 写在平面直角坐标系上,那么 每一列只有一个 P P P(因为其它的都转移成它)。

我们继续寻找 f 3 , k = P f_{3,k} = P f3,k=P k k k 值。

下面,我们用 x 1 x^1 x1 表示 x x x 状态的取反值。(取反即 P ← A P \gets A PA A ← P A \gets P AP

f 3 , 4 = f ( 1 , 2 ) 1 = P 1 = A f_{3,4} = f(1,2)^1 = P^1 = A f3,4=f(1,2)1=P1=A

f 3 , 5 = max ⁡ ( f 3 , 4 , f 3 , 3 , f 2 , 4 , f 1 , 3 ) 1 = A 1 = P f_{3,5}=\max(f_{3,4},f_{3,3},f_{2,4},f_{1,3})^1 = A^1 = P f3,5=max(f3,4,f3,3,f2,4,f1,3)1=A1=P

max ⁡ \max max 即在所有状态中取最优解(能赢则赢,全输则输)。

因此, f 1 , 2 f_{1,2} f1,2 f 3 , 5 f_{3,5} f3,5 P P P 态。

然后经过类似枚举发现:(其实是用电脑动态规划打表的

f 1 , 2 , f 3 , 5 , f 4 , 7 , f 6 , 10 , f 8 , 13 = P f_{1,2} , f_{3,5},f_{4,7},f_{6,10},f_{8,13} = P f1,2,f3,5,f4,7,f6,10,f8,13=P

这时我们大概发现了一点规律。

首先,第 i i i P P P 态一定是 f x , x + i f_{x,x+i} fx,x+i 的形式。

你发现这个数列有极了规律。

i i i P P P 态应当时 f ( m i , m i + i ) f(m_i,m_i+i) f(mi,mi+i),其中 m i m_i mi 表示前 i − 1 i-1 i1必败态中未出现的最小数

什么意思?比方说,第 3 3 3 P P P 态。

前面出现过 1 , 2 , 3 , 5 1,2,3,5 1,2,3,5 ∴ \therefore 就是 f 4 , 4 + 3 = f 4 , 7 f_{4,4+3} = f_{4,7} f4,4+3=f4,7

然后,给出一段伪代码,求出前 1 1 1 ~ 1 0 5 10^5 105 P P P 态。

freopen("data.txt","w",stdout);
	bool h[1000001]; int last=1; //last表示上次出现的位置,用来优化搜索,保证线性
	memset(h,0,sizeof(h)); //桶清0
	for(int i=1;i<=100000;i++) {
		for(int j=last;j<=INT_MAX;j++)
			if(!h[j]) {
				last=j; h[j]=1; h[j+i]=1;
				printf("%d %d\n",j,j+i);
				break;
			}
	}

时间复杂度是 O ( 1 0 5 ) O(10^5) O(105),然后我们得到一个表:

打表真快乐

(如果你想分析,也可以试着分析一下这种大数据)

(天哪,你不会以为我想把 1 0 9 10^9 109 的表滚出来然后交吧,不好意思)

然后经过 看题解 分析发现:

m i = i × 1 + 5 2 m_i = i \times \frac{1 + \sqrt{5}}{2} mi=i×21+5

然后直接判断即可。

时间复杂度: O ( n ) O(n) O(n).

期望得分; 100 p t s 100pts 100pts.

实际得分: 90 p t s 90pts 90pts.(始终不知道怎么错的,但 POJ \texttt{POJ} POJ 上交是 AC \text{AC} AC 的)

#include<math.h>
#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;

typedef long long ll; //后来调试无果开了 long long

inline ll read(){char ch=getchar();int f=1;while(ch<'0' || ch>'9') {if(ch=='-') f=-f; ch=getchar();}
	ll x=0;while(ch>='0' && ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();return x*f;}

int main(){
	ll a,b;
	while(scanf("%lld%lld",&a,&b)!=EOF) {
		if(a<b) swap(a,b);
		a=(int)((a-b)*(1+sqrt(5))/2.0);
		if(a==b) puts("0");
		else puts("1");
	}
	return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值