HDOJ 6869 Slime and Stones(杭电多校2020第九场1003)(威佐夫博弈+二分)可能是比较无脑的做法

2 篇文章 0 订阅
1 篇文章 0 订阅

题目链接: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[n1]=a[n]a[n1]+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]=n25 +1,b[n]=a[n]+n;
我的解法其实弄到这里再二分找合适的n就足够了但是补充一下可以用
( y − x ) ∗ 5 + 1 2 = x (y−x)∗\frac{\sqrt{5}+1}{2}=x (yx)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[n1]=a[n]a[n1]+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();
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值