Day2

今天讲了博弈论的相关知识,感觉很注重思维和模型积累的是SG函数,应该是需要多做题才可以熟练掌握。

聚聚帮忙列出了几种应该掌握的博弈模型:

  • 巴什博弈
  • 威佐夫博弈
  • 斐波那契博弈
  • 尼姆博弈
  • 阶梯博弈
  • Nim博弈的第二种形式
  • 树上博弈

下来总结今日学习的题目:

A - Playing With Stones

题意:n堆stones,一次最多只能取走一半stones,stone为1时失败,先手是否胜利。
Thinking:

  • 打表法找出SG函数的规律。
  • 用亦或表示P-N状态。
  • 利用多组Nim博弈的结论进行判断。
#include <cstdio>
#include <cstring>
#define LL long long
const int maxn = 1e3+10;
/*
int SG[maxn], S[maxn];
void getSG(int n){
    memset(SG, 0, sizeof(SG));
    //sg[0]=0;  sg[1]=0;
    for(int i=2; i<=n; i++){
        memset(S, 0, sizeof(S));
        for(int j=1; j*2<=i; j++){
            S[SG[i-j]] = 1;
        }
        for(int j=0; ;j++){
            if(!S[j]){
                SG[i] = j;
                break;
            }
        }
    }
}
int main(){
    getSG(100);
    for(int i=1; i<=50; i++){
        if(i%10==0)    printf("\n");
        printf("%d ",SG[i]);
    }
    return 0;
}
*/
LL getsg(LL n){
    return n%2 ? getsg(n/2) : n/2;
}
int main(){
    int T,N;
    scanf("%d",&T);
    for(int t=0; t<T; t++){
        scanf("%d",&N);
        LL ans = 0;
        for(int i=0; i<N; i++){
            LL tmp;
            scanf("%lld",&tmp);
            ans ^= getsg(tmp);
        }
        if(ans){
            printf("YES\n");
        }else{
            printf("NO\n");
        }
    }
    return 0;
}

B - 悼念512汶川大地震遇难同胞——选拔志愿者

C - Public Sale

Thinking:
很明显的巴什博弈。
策略:一堆n个石子,最多取m个,最少1个,A先手。 n%(m+1)==0 :B胜; 否则A开始取n-n/(m+1)*(m+1)个石子,之后石子和始终为(m+1),即A胜。

#include <cstdio>
int main(){
    int C;
    scanf("%d",&C);
    for(int c=0; c<C; c++){
        int n,m;
        scanf("%d%d",&n, &m);
        if(n%(m+1) == 0){
            printf("Rabbit\n");
        }else{
            printf("Grass\n");
        }
    }
    return 0;
}
#include <cstdio>
int main(){
    int n,m;
    while( scanf("%d%d",&m,&n) != EOF){
        if(m%(n+1) == 0){
            printf("none\n");
        }else if(m <= n){
            for(int i=m; i<=n; i++){
                printf("%d%s",i,i==n?"\n":" ");
            }
        }else{
            printf("%d\n",m-(m/(n+1))*(n+1));
        }
    }
    return 0;
}

D - No Gambling

认真读题就好了
题意:
给定两个N*N由红点和蓝点构成的完全对称的图,两个人轮流连边,每次只能对相邻同颜色两点连一条边且不能穿过对方所连的线,先手对蓝点连边,从左到右获胜,后手对红点连边,从上到下获胜,问谁胜谁负。

#include <cstdio>
int main(){
    int n;
    while(scanf("%d",&n) && (n+1)){
        printf("I bet on Oregon Maple~\n");
    }
    return 0;
}

E - Good Luck in CET-4 Everybody!

Thinking:
开始产生错误思路:看到2的幂,想到利用数的二进制和位运算来判断,但发现当n=21=1+4+16时答案错误。
正解:sg打表找规律。关于规律为3的倍数先手胜利的思考:这是保证最后的1由先手来取,先手操作时始终保持留给对手的是3的倍数即可。

/*
int f[11];
bool sg[1005];
void init(){
    f[0]=1;
    for(int i=1; i<=10; i++)    f[i] = f[i-1]*2;
    for(int i=1; i<=1000; i++){
        for(int j=0; f[j]<=i; j++){
            if(!sg[i-f[j]]){
                sg[i]=true;
                break;
            }
        }
    }
}
*/
#include <cstdio>
int main(){
    int n;
    while(scanf("%d",&n) != EOF){
        if(n%3==0){
            printf("Cici\n");
        }else{
            printf("Kiki\n");
        }
    }
    return 0;
}

F - Coin Game

题意: 将给定的n个硬币排成一圈,两人轮流取硬币,每次只能取连续的1到k个硬币,问先手胜还是负。
如果能够把游戏分成对称的两部分,那么先手在某一部分里做了某个操作,后手就可以在另一部分里做和它同样的操作。最后肯定是先手先结束他自己的那一部分,后手再结束另一部分,那么后手就胜利了。

#include <cstdio>
int main(){
    int T;
    scanf("%d",&T);
    for(int t=1; t<=T; t++){
        int n,k;
        scanf("%d%d",&n, &k);
        if(k==1 && (n%2==1)){
            printf("Case %d: first\n",t);
        }else if(n <= k){
            printf("Case %d: first\n",t);
        }else{
            printf("Case %d: second\n",t);
        }
    }
    return 0;
}

G - 取石子游戏

斐波那契博弈。

/*
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e3+10;
int sg[maxn][maxn];
int dfs(int x, int y){
    if(sg[x][y] != -1)    return sg[x][y];
    set<int> s;
    s.clear();
    for(int i=1; i<=y; i++){
        if(x-i >= 0){
            s.insert(sg[x-i][2*i]);
        }
    }//属于这个集合的非负整数
    for(int i=0; ;i++){
        if(s.count(i) == 0){
            sg[x][y] = i;
            break;
        }
    }//不属于这个集合的最小非负整数 //这里用set()//A题用的是类似与hash的方法
    return sg[x][y];
}
int main()
{
    memset(sg, -1, sizeof(sg));
    for(int i=1; i<=1000; i++){
        sg[0][i] = 0;
        sg[1][i] = 1;
    }
    for(int i=2; i<=100; i++){
        for(int j=1; j<=100; j++){
            sg[i][j] = dfs(i, j);
        }
    }
    for(int i=1; i<=30; i++){
        for(int j=1; j<=30; j++){
            printf("%2d ",sg[i][j]);
        }
        cout << endl;
    }
    bool flag = true;
    for(int i=1; i<=100; i++){
        for(int j=1; j<i; j++){
            if(sg[i][j]!=0){
                flag=false;  break;
            }
        }
        if(flag){
            cout << i << endl;
            flag = true;
        }else{
            flag = true;
        }
    }
    return 0;
}
*/
#include <bits/stdc++.h>
using namespace std;
#define LL long long
const int maxn = 50;
LL f[maxn];
void fib(){
    f[1]=1; f[2]=2;
    for(int i=3; i<maxn; i++){
        f[i] = f[i-1] + f[i-2];
    }
}
int main(){
    fib();
    LL n;
    while(scanf("%lld",&n) != EOF && (n!=0)){
        if(lower_bound(f, f+50, n)-f>=50 || f[lower_bound(f, f+50, n)-f]!=n){
            printf("First win\n");
        }else{
            printf("Second win\n");
        }
    }
    return 0;
}

H - Nim or not Nim?

题意:Alice和Bob轮流取石子,每一次可以从任意一堆中拿走任意个石子,也可将一堆石子分为两个小堆。求必胜策略。
SG函数打表找规律。

#include <cstdio>
#include <cstring>
/*
const int N = 1e6+5;
int vis[N], sg[N];
void getSG(){
    memset(sg, 0, sizeof(sg));
    sg[0] = 0;
    sg[1] = 1;
    for(int i=1; i<100; i++){
        memset(vis, 0, sizeof(vis));
        for(int j=1; j<=i; j++){
            vis[sg[i-j]] = 1;
        }//移走任意个石子
        for(int j=1; j<i; j++){
            vis[sg[j]^sg[i-j]] = 1;
        } //将石子分为两堆
        for(int j=0; ; j++){
            if(!vis[j]){
                sg[i] = j;
                break;
            }
        }
        printf("%d %d\n", i, sg[i]);
    }
}
*/
int getsg(int n){
    if(n%4==3)    return n+1;
    else if(n%4==0)    return n-1;
    else    return n;
}
int main(){
    int T;
    scanf("%d", &T);
    for(int t=0; t<T; t++){
        int N,ans = 0;
        scanf("%d", &N);
        for(int i=0; i<N; i++){
            int tmp;
            scanf("%d", &tmp);
            ans ^= getsg(tmp);
        }
        if(ans == 0){
            printf("Bob\n");
        }else{
            printf("Alice\n");
        }
    }
    return 0;
}

K - Being a Good Boy in Spring Festival

题意:有m堆牌,两个人先后取某堆中的任意(不少于一)张牌,最后取完者胜;问先手取胜第一次取牌有多少种取法。

Thinking:
利用亦或的性质,可以看成是自反性吧,然后逆着思考 必胜的情况下 首次操作 可以对哪些堆,可以保证亦或和为0;并且要符合实际,即 所取数量<=已有数量(此题小于也可以过,=应该更严谨吧)

#include <cstdio>
int a[105];
int main(){
    int m;
    while( scanf("%d",&m)!=EOF &&  m ){
        int ans = 0;
        for(int i=0; i<m; i++){
            scanf("%d",&a[i]);
            ans ^= a[i];
        }
        if(ans == 0){
            printf("0\n");
        }else{
            int num=0;
            for(int j=0; j<m; j++){
                if((ans^a[j]) <= a[j]){
                    num++;
                }
            }
            printf("%d\n",num);
        }
    }
    return 0;
}

L - 取(m堆)石子游戏

Nim博弈,与K相似。

#include <cstdio>
const int maxm = 200005;
int a[maxm];
int main(){
    int m;
    while( scanf("%d",&m)!=EOF && m ){
        int ans=0;
        for(int i=0; i<m; i++){
            scanf("%d", &a[i]);
            ans ^= a[i];
        }
        if(ans == 0){
            printf("No\n");
        }else{
            printf("Yes\n");
            for(int i=0; i<m; i++){
                int tmp = ans^a[i];
                if(tmp <= a[i]){
                    printf("%d %d\n", a[i], tmp);
                }
            }
        }
    }
    return 0;
}

M - 取石子游戏

Thinking:
很直接的威佐夫博弈,由此题可知积累模型的重要性,否则,这个题打表肉眼恐怕很难找到规律。

#include <cstdio>
#include <cmath>
int main(){
    int a,b;
    while(scanf("%d%d", &a, &b)!=EOF){
        if(a > b){
            int t=a; a=b; b=t;
        }
        int i = b-a;
        b = (int)((sqrt(5.0)+1.0)/2.0*i);
        if(a==b)    printf("0\n");
        else    printf("1\n");
    }
    return 0;
}
原文地址:https://www.cnblogs.com/seaupnice/p/9410938.html