博弈论题集&构造题集

2021/04/19 ABS https://vjudge.net/problem/AtCoder-arc085_b/origin

题意:

两个人X和Y手中两张卡数值为Z和W。现在有N张卡,两人轮流取(取至少一张 可以全取完)。然后只留下最后取的一张卡。X想让他和Y手中卡数值差距绝对值最大,Y想让最小。

求两者都采取最佳策略下最后的数值差距

题解:

X取最后一张或者倒数第二张,取最优差距。

#include <stdio.h>
#include <cstring>
#include <cmath>
#include <iostream>
using namespace std;
typedef long long ll;
const ll mx=2e3+10;
ll N, Z, W;
ll a[mx];
void solve(){
    scanf("%lld %lld %lld", &N, &Z, &W);
    for(ll i=1;i<=N;i++)scanf("%lld", &a[i]);
    if(N==0){
        printf("%lld\n", abs(Z-W));
        return;
    }
    if(N==1){
        printf("%lld\n", abs(a[1]-W));
        return;
    }
    ll ans1=abs(a[N-1]-a[N]);
    ll ans2=abs(a[N]-W);
    printf("%lld\n", max(ans1, ans2));
}
int main(){
    solve();
    return 0;
}
ABS

 

2021/05/05 

题目:

https://vjudge.net/contest/387837#problem/A

https://vjudge.net/problem/POJ-2234/origin

题意:

有n堆石子,每个人随便从某一堆取任意个(至少为1),谁无法取了谁就输。问谁输

题解:

https://www.cnblogs.com/zlrrrr/p/10596717.html

好愁 已经把博弈论内容全忘了

sg函数

对于任意状态 x , 定义 SG(x) = mex(F),其中F 是 x 后继状态的SG函数值的集合(就是上述mex中的数值)。最后返回值(也就是SG(X))为0为必败点,不为零必胜点。

#include <iostream>
#include <cstring>
#include <cstdio>
typedef long long ll;
const int mx=1e3+10;
using namespace std;
ll sg[mx];
bool has[mx];
/*
对于任意状态 x , 定义 SG(x) = mex(F),
其中F 是 x 后继状态的SG函数值的集合(就是上述mex中的数值)。
最后返回值(也就是SG(X))为0为必败点,不为零必胜点。
*/
void getSg(ll len){
    memset(sg, 0, sizeof(sg));
    for(ll i=1;i<len;i++){//枚举当前石子的个数
        memset(has, false, sizeof(has));
        for(ll j=1;j<=i;j++){
            has[i-j]=true;
        }
        for(ll j=0;;j++){
            if(has[j]==false){
                sg[i]=j;
                break;
            }
        }
    }
}

int main(){
//    getSg(1e3);
    ll num, tmp;
    while(scanf("%lld", &num)!=EOF){
        ll v=0;
        for(ll i=1;i<=num;i++){
            scanf("%lld", &tmp);
            v=v^tmp;
        }
        printf("%s\n", v==0?"No":"Yes");//判断条件不能为v>0
    }
    return 0;
}
ac

 

2021/05/14

题目:

https://vjudge.net/contest/387837#problem/B

https://vjudge.net/problem/POJ-1704/origin

题意:

n个棋子放在一行上,第i个棋子的位子为pi(1<=n<=1000, 1<=pi<=10000)。b和g两人轮流右移一个棋子任意步数(但是每次移动不能超过右边的棋子

题解:

可以把题意转化为nim。令任意相邻的两个棋子之间的空的个数为一堆石子的个数。那么移动一个棋子x步,就是往一堆石子里面拿走x个,往另一个石子堆里面添加x个。

#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdio>
typedef long long ll;
const int mx=1e3+10;
using namespace std;
ll tmp[mx];
int main(){
    ll t;
    scanf("%lld", &t);
    for(ll time=1;time<=t;time++){
        ll num, v=0;
        scanf("%lld", &num);
        for(ll i=1;i<=num;i++){
            scanf("%lld", &tmp[i]);
        }
        sort(tmp+1, tmp+num+1);
        ll i;
        if(num&1){
            v=tmp[1]-1;
            i=2;
        }
        else{
            v=0;
            i=1;
        }
        for(;i+1<=num;i++){
            v=v^(tmp[i+1]-tmp[i]-1);
        }
        printf("%s\n", v==0?"Bob will win":"Georgia will win");
    }

    return 0;
}
ac

 

2021/05/19

题目:C - GG and MM

https://vjudge.net/contest/387837#problem/C

http://acm.hdu.edu.cn/showproblem.php?pid=3595

题意:everysg

n组游戏,每组有两堆石子,个数为p和q。每个人每轮,令少的一堆个数为x,大的个数为y,那么可以从y里面拿走kx个(kx<=y)。每轮都要把每一组能继续执行的游戏执行以上操作。

最后什么操作都无法执行的人输。

题解:everysg

https://www.cnblogs.com/zwfymqz/p/8470381.html

-对于SG值为0的点,我们需要知道最少需要多少步才能走到结束【必败态】

-对于SG值不为0的点,我们需要知道最多需要多少步结束【必胜态】

 用step变量来记录这个步数

对于Every-SG游戏先手必胜当且仅当单一游戏中最大的step为奇数。

#include<iostream>
#include<algorithm>
#include<vector>
#include<cstring>
using namespace std;
const int mx=1e3+10;
const int inf=1<<30;
typedef long long ll;
ll N, p, q;
ll sg[mx][mx], step[mx][mx];
ll getsg(ll x, ll y){
    if(sg[x][y]!=-1)return sg[x][y];
    if(x>y)swap(x, y);
    //x<y
    ll k=y/x, r=y%x;
    if(k==1){
        ll i=y-x;//只有一种做法
        sg[x][y]=sg[y][x]=getsg(x, i)^1;
        step[x][y]=step[y][x]=step[x][i]+1;
    }
    else{
        //当前有两种走法 所以当前必然赢
        sg[x][y]=sg[y][x]=1;
        ll v=getsg(x, r);
        //如果v==0 说明后一个人必然输
        //如果v==1 说明要留一步给对方 然后再加上1步
//        step[x][y]=step[y][x]=getsg(x, r)+step[x][r]+1;
        if(v==0){//后手必然输这种走法
            getsg(x, r);
            step[x][y]=step[y][x]=step[x][r]+1;
        }
        else{
            getsg(x, r+x);
            step[x][y]=step[y][x]=step[x][r+x]+1;
        }
    }

    return sg[x][y];
}
int main(){
    memset(sg, -1, sizeof(sg));
    for(ll i=0;i<mx;i++){
        sg[i][0]=sg[0][i]=0;//先手必输
        step[i][0]=step[0][i]=0;
    }

    while(scanf("%lld", &N)!=EOF){
        ll maxstep=0;
        for(ll i=1;i<=N;i++){
            scanf("%lld %lld", &p, &q);
            getsg(p, q);
            maxstep=max(maxstep, step[p][q]);
        }
        printf("%s\n", maxstep&1?"MM":"GG");
    }
    return 0;
}
ac

wa了很久。。

刚开始没有理解参考题解里面的k。后来想明白了。如果k>2,先手想要不给对方控制的机会,那么要么直接取y%x,这样对方就只能调换取石子的堆了(因为两个堆里哪个堆的石子多 变了)。也可以只留下y%x+x,这样对方就只能在当前较小的堆里取且只能取一个x。

那么对于k==1的情况,就直接取,sg函数异或1切换。

对于k==0的情况 题解有个公式一直妹看懂后来理解了。。。

step[x][y]=step[y][x]=getsg(x, r)+step[x][r]+1;

如果getsg(x,r)为0,说明取到头的时候后手必输,sg=0,步数直接+1就行了,也就是step[x][r]+1,此时sg=0不影响结果;

反之不为0,为1。说明我们不能取到头,要给对方留一步,让对方去走不好的路。那么从x,y到x,r中间还要多加一步x,r+x。所以应该是step[x][r]+1+1, 而此时sg为1,所以可以直接用sg来代表后面新增的1。

 

2021/05/19

题目:E - 取石子游戏

https://vjudge.net/contest/387837#problem/E

http://acm.hdu.edu.cn/showproblem.php?pid=2516

题意:

一堆石子n个。第一次取任意个(不能全取完)。之后每次取不能超过之前的两倍(可以全取完)。取完的人胜。

题解:斐波那契博弈。。。(真的全忘了。。

https://blog.csdn.net/acm_cxlove/article/details/7835016

“Zeckendorf定理”(齐肯多夫定理):任何正整数可以表示为若干个不连续的Fibonacci数之和。

呜呜呜太难了太难了

#include<iostream>
#include<algorithm>
#include<vector>
#include<map>
#include<cstring>
using namespace std;
const int mx=50+10;
const int inf=1<<30;
typedef long long ll;
ll f[mx];
map<ll, ll>mp;
void init(){
    f[1]=1, f[2]=2;
//    ll v=(ll)1<<32;
    for(ll i=3;i<mx;i++){
        f[i]=f[i-1]+f[i-2];
        mp[f[i]]=1;
    }
}
int main(){
    init();
    ll v;
    while(1){
        scanf("%lld", &v);
        if(v==0)break;
        printf("%s win\n", mp[v] == 1 ? "Second" : "First");
    }
    return 0;
}
ac

 

2021/05/19

题目:F - A Multiplication Game

https://vjudge.net/contest/387837#problem/F

http://acm.hdu.edu.cn/showproblem.php?pid=1517

题意:

s和o玩游戏,s从1开始,接下来每人轮着从2到9选一个数字乘上去,谁先大于等于n谁就赢。

题解:

从后往前想。谁大于n谁就赢。那么也就是说 谁先大于n2=ceil(n/9.0)并且够不到n谁就输,因为后手必然可以到达n。

继续推,谁先到达n3=ceil(n/2.0)谁就赢,因为它可以确保后手到达n2且无法到达n。

。。。

以此类推。

只需要最后判断谁是赢的先手就可以了。

#include<iostream>
#include<algorithm>
#include<vector>
#include<map>
#include<cmath>
#include<cstring>
using namespace std;
const int mx=50+10;
const int inf=1<<30;
typedef long long ll;

ll time(ll v, ll id){
    if(v==1) return 0;
    v=(id==1?ceil(v/9.0):ceil(v/2.0));
    return time(v, id^1)+1;
}
void solve() {
    ll v;
    while(scanf("%lld", &v)!=EOF){
        ll t=time(v, 1);
        printf("%s wins.\n", t & 1 ? "Stan" : "Ollie");
    }
}
int main(){
    solve();
    return 0;
}
ac

此外,我看到了我九个月前的代码。。当时是从前往后推,只要一直乘以18就行。然后最后判断x9能不能到达n。能就是stan赢。(其实就是把倒过来的思路扳回去了)

 

posted @ 2021-04-19 15:44  反射狐  阅读(56)  评论(0编辑  收藏  举报