题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6869
题意:给a和b两堆石头和一个k,每次可以进行如下两种操作的其中之一
1.在其中一堆中取任意数量个石头
2.在一堆取x个,另一堆取y个,但是限制|x-y|<=k
普通的威佐夫:
(会的直接跳到题解部分就行了)
普通的威佐夫博弈就是这个问题的简化版,即k=0的情况
参考了这篇帖子:https://blog.csdn.net/NIV__/article/details/54587849
定义奇异局势(必败态)为一个二元组<a,b>,人为规定a要<=b,在这种状态下必输
显而易见的一种奇异局势是<0,0>
那么怎么得到其他的奇异局势?
“先标出(0,0),然后划去所有(0,k),(k,0),(k,k)的格点;然后找y=x上方未被划去的格点,标出(1,2),然后划去(1,k),(k,2),(1+k,2+k),同时标出对称点(2,1),划去(2,k),(1,k),(2+k,1+k);然后在未被划去的点中在y=x上方再找出(3,5)。。。按照这样的方法做下去,如果只列出a<=b的必败态的话,前面的一些是(0,0),(1,2),(3,5),(4,7),(6,10),…”–百度百科
我们考虑<0,0>可以划掉<1,x>中的多少元素,即这个x可以划掉
注:这里的<2,x>,x<=2因为规定了a<=b,所以默认x>=2的。
那么接下来考虑最小的一个没被划掉的二元组,显然是<1,2>
从1到n增加了(n-1),所以2增加(n-1)的也会被划掉
下次取a=3,b只能往2+(3-1)再加一个即2+(3-1)+1上取
我们找到的这个规律就是
b
[
n
]
−
b
[
n
−
1
]
=
a
[
n
]
−
a
[
n
−
1
]
+
1
;
b[n]-b[n-1]=a[n]-a[n-1]+1;
b[n]−b[n−1]=a[n]−a[n−1]+1;
通过边界条件
b
[
0
]
=
a
[
0
]
=
0
b[0]=a[0]=0
b[0]=a[0]=0
可以推得
b
[
n
]
=
a
[
n
]
+
n
b[n]=a[n]+n
b[n]=a[n]+n
但是这里我们只知道b和a关于下标n的关系,每次找出新的(之前的二元组中没出现过)而且是最小的a和b.
这里要用到beatty定理,详见百度百科证明:https://baike.baidu.com/item/%E8%B4%9D%E8%92%82%E5%AE%9A%E7%90%86/2677437
设
a
[
n
]
=
⌊
n
α
⌋
,
b
[
n
]
=
⌊
n
β
⌋
,
满
足
1
α
+
1
β
=
1
,
且
α
和
β
都
是
无
理
数
a[n]=\lfloor nα\rfloor,b[n]=\lfloor nβ\rfloor,满足\frac{1}{α}+\frac{1}{β}=1,且α和β都是无理数
a[n]=⌊nα⌋,b[n]=⌊nβ⌋,满足α1+β1=1,且α和β都是无理数
这里引入之前的方程
b
[
n
]
=
a
[
n
]
+
n
,
有
n
β
=
n
α
+
n
,
β
=
α
+
1
b[n]=a[n]+n,有nβ=nα+n,β=α+1
b[n]=a[n]+n,有nβ=nα+n,β=α+1
得到
1
α
+
1
α
+
1
=
1
计
算
解
得
α
=
5
+
1
2
\frac{1}{α}+\frac{1}{α+1}=1计算解得α=\frac{\sqrt{5}+1}{2}
α1+α+11=1计算解得α=25+1
因此第n个位置的
a
[
n
]
=
⌊
n
∗
5
+
1
2
⌋
,
b
[
n
]
=
a
[
n
]
+
n
;
a[n]=\lfloor n*\frac{\sqrt{5}+1}{2}\rfloor,b[n]=a[n]+n;
a[n]=⌊n∗25+1⌋,b[n]=a[n]+n;
我的解法其实弄到这里再二分找合适的n就足够了但是补充一下可以用
(
y
−
x
)
∗
5
+
1
2
=
x
(y−x)∗\frac{\sqrt{5}+1}{2}=x
(y−x)∗25+1=x
来判断。
题解
按照之前的思路,考虑<an,bn>可以划掉哪些二元组
看<an,bn>对an+1所在的二元组集合{<a,x>|x∈z}有什么影响,划掉了其中的哪些
解释一下这张图,<an,bn>,如果an增加了x,那么bn最多可以增加x+k。
新的bn+1就只能取bn - (an+1 - an) + k再+1了
所以就有了这条式
b
[
n
]
−
b
[
n
−
1
]
=
a
[
n
]
−
a
[
n
−
1
]
+
k
+
1
;
b[n]-b[n-1]=a[n]-a[n-1]+k+1;
b[n]−b[n−1]=a[n]−a[n−1]+k+1;
通过边界条件
b
[
0
]
=
a
[
0
]
=
0
b[0]=a[0]=0
b[0]=a[0]=0
可以推得
b
[
n
]
=
a
[
n
]
+
n
∗
(
k
+
1
)
b[n]=a[n]+n*(k+1)
b[n]=a[n]+n∗(k+1)
根据Beatty定理
设
a
[
n
]
=
⌊
n
α
⌋
,
b
[
n
]
=
⌊
n
β
⌋
,
满
足
1
α
+
1
β
=
1
,
且
α
和
β
都
是
无
理
数
a[n]=\lfloor nα\rfloor,b[n]=\lfloor nβ\rfloor,满足\frac{1}{α}+\frac{1}{β}=1,且α和β都是无理数
a[n]=⌊nα⌋,b[n]=⌊nβ⌋,满足α1+β1=1,且α和β都是无理数
这里引入之前的方程
b
[
n
]
=
a
[
n
]
+
n
,
有
n
β
=
n
α
+
n
∗
(
k
+
1
)
,
β
=
α
+
k
+
1
b[n]=a[n]+n,有nβ=nα+n*(k+1),β=α+k+1
b[n]=a[n]+n,有nβ=nα+n∗(k+1),β=α+k+1
通过这条式子用k解α
1
α
+
1
α
+
k
+
1
=
1
\frac{1}{α}+\frac{1}{α+k+1}=1
α1+α+k+11=1
因此第n个位置的
a
[
n
]
=
⌊
n
∗
α
⌋
,
b
[
n
]
=
a
[
n
]
+
n
∗
(
k
+
1
)
;
a[n]=\lfloor n*α \rfloor,b[n]=a[n]+n*(k+1);
a[n]=⌊n∗α⌋,b[n]=a[n]+n∗(k+1);
我们一共做两次二分,第一次二分找到合适的α。
第二次去找到合适的下标n使
a
[
n
]
=
⌊
n
∗
α
⌋
a[n]=\lfloor n*α \rfloor
a[n]=⌊n∗α⌋
之后只需要检查b是否符合
b
[
n
]
=
a
[
n
]
+
n
∗
(
k
+
1
)
b[n]=a[n]+n*(k+1)
b[n]=a[n]+n∗(k+1)即可
和题解的方法比可以说无脑了很多
代码:
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<vector>
#include<map>
#include<queue>
#include<cmath>
#include<functional>
using namespace std;
template<class T>inline void read(T &x){x=0;char o,f=1;while(o=getchar(),o<48)if(o==45)f=-f;do x=(x<<3)+(x<<1)+(o^48);while(o=getchar(),o>47);x*=f;}
int cansel_sync=(ios::sync_with_stdio(0),cin.tie(0),0);
#define ll long long
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define repb(i,a,b) for(int i=(a);i>=b;i--)
#define INF 0x3f3f3f3f
#define cendl printf("\n")
ll gcd(ll a,ll b){ while(b^=a^=b^=a%=b); return a; }
//#define INF 0x7fffffff
ll a,b,k;
const double eps = 1e-12;
inline double cal(int k,double a){
return 1/(a+k+1)+1/a;
}
void solve(){
cin>>a>>b>>k;
if(a>b) swap(a,b);
double l = 1,r = 2;//alpha的区间应该是在1和2之间的
double now=0,mid;
while(abs(now-1.0)>eps){//第一次二分寻找合适的alpha
mid = (l+r)/2;
now = cal(k,mid);
if(now>1.0) l = mid;
else r = mid;
}
double alpha = mid;//通过二分计算得到alpha
int lb = 0,rb = 5e7+5;
int midd;
while(lb+1<rb){//第二次二分求a对应第几个二元组
midd = (lb+rb)/2;
if(floor(midd*alpha)>a) rb = midd;
else lb = midd;
}
if(floor(lb*alpha)==a&&a+(k+1)*lb==b) cout<<0<<endl;
else cout<<1<<endl;
}
int main(){
int z;
cin>>z;
while(z--) solve();
}