博弈论 sg函数

基础结论:
巴什博弈:一堆n个物品,两个人轮流从这堆物品中取物,规定每次至少取一个,最多取m个,最后取光者得胜
结论: n n%(m+1)==0 n先手必输

斐波那契博弈:有一堆个数为n(n>=2)的石子,先手不能在第一次把所有的石子取完,至少取1颗,之后每次可以取的石子数至少为1,至多为对手刚取的石子数的2倍,取走最后一个石子的人为获胜
结论:如果 n n n 是一个斐波那契数,那么先手必败

威佐夫博弈:有两堆各若干个物品,两个人轮流从某一堆或同时从两堆中取同样多的物品,规定每次至少取一个,多者不限,最后取光者得胜
结论: ⌊ ( b − a ) ∗ ( 5 + 1 ) 2 ⌋ = a \lfloor {(b-a)*(\sqrt {5}+1) \over 2} \rfloor =a 2(ba)(5 +1)=a先手必败
poj 1067

nim博弈:有 n n n 堆各若干个物品,两个人轮流从某一堆取任意多的物品,最后取光者得胜。
结论: a 1 a_1 a1^ …^ a n = 0 a_n=0 an=0先手必败
洛谷 P2197

anti-nim:有 n n n 堆各若干个物品,两个人轮流从某一堆取任意多的物品,最后取光者输。
结论:

1.有个数大于1的堆,异或和不为0,先手必胜
2.没有个数大于1的堆,异或和为0,先手必胜
( f l a g & & s u m ) ∥ ( ! f l a g & & ! s u m ) (flag\&\&sum) \| (!flag\&\&!sum) (flag&&sum)(!flag&&!sum)

阶梯博弈:有n个阶梯呈升序排列,每个阶梯上有若干个石子,可行的操作是将一个阶梯上的石子移任意个(>0)到前一个台阶。当没有可行操作时输
结论:看作奇数台阶的nim博弈

树上删边博弈:给定一棵有根树,A和B分别轮流删边,删边后不与根联通的子树也一并删去,最后不能删边就输了
结论:子树根节点 s g u = s g v 1 sg_u=sg_{v_1} sgu=sgv1^ … ^ s g v n sg_{v_n} sgvn

HDU - 1404
(1)先手必胜的状态一定能转移到某一个先手必败的状态
(2)先手必败的状态能转移到的状态一定是先手必胜

打表每个数字是必胜点还是必败点
特判开头为0的字符串:必胜
其它情况可以通过打表得到的数组直接判断

打表:0是必胜点
枚举 i i i:1-1000000,对于必败点,我们通过题意的操作回退到的点一定是必胜点
操作一:将 i i i的某一位数字增加
操作二:将 i i i后加一个0,0后随意加数字

HDU - 2873
二维sg函数

对于边界炸弹: ( 0 , i ) , ( i , 0 ) (0,i),(i,0) (0,i),(i,0),可看做nim取石子,sg值都为 i i i
然后枚举 i , j i,j i,j打表,套getsg板子,要注意的是操作一次,炸弹会变成两个,相较于之前要么行坐标改变,要么列坐标改变,因此枚举操作 i , j i,j i,j点后的两个点,求两个sg的异或和,再枚举得出mex确定 s g i , j sg_{i,j} sgi,j

HDU - 1809
将二维数组转化为字符串,然后sg暴力打表

#include <bits/stdc++.h>
#define ll long long
#define ull unsigned long long
std::map<std::string,int> sg;
std::map<std::string,bool> vis;
int n,m;
int dfs(std::string s){
    if (vis[s]){
        return sg[s];
    }
    vis[s]=1;
    std::vector<int> flag(25);
    for (int i=0;i<s.length();i++){
        if ((i+1)%m==0||i/m==n-1) continue;
        if (s[i]=='0'&&s[i+1]=='0'&&s[i+m]=='0'&&s[i+1+m]=='0'){
            std::string temp=s;
            temp[i]=temp[i+1]=temp[i+m]=temp[i+m+1]='1';
            int x=dfs(temp);
            if (x<=20){
                flag[x]=1;
            }
        }
    }

    while (flag[sg[s]]){
        sg[s]++;
    }
    return sg[s];
}
void yrzr(){
    int q,ans=0;
    std::cin>>q;
    while (q--){
        std::cin>>n>>m;
        std::string s;
        for (int i=1;i<=n;i++){
            std::string ss;
            std::cin>>ss;
            s+=ss;
        }
        ans^=dfs(s);
    }
    std::cout<<(ans?"Yes\n":"No\n");
}
int main(){
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout.tie(nullptr);

    int T=1;
    // std::cin>>T;
    while (T--){
        yrzr();
    }
    return 0;
}

HDU - 1524
图上跑sg,图不一定连通

#include <bits/stdc++.h>
#define ll long long
#define ull unsigned long long
void yrzr(){
    int n;
    std::cin>>n;
    std::vector<std::vector<int>> e(n);
    for (int i=0;i<n;i++){
        int k;
        std::cin>>k;
        for (int j=1;j<=k;j++){
            int x;
            std::cin>>x;
            e[i].push_back(x);
        }
    }

    std::vector<int> sg(n+1),vis(n+1);
    std::function<int(int)> dfs=[&](int u){
        if (vis[u]){
            return sg[u];
        }
        vis[u]=1;
        std::vector<int> ok(n+2);
        for (auto v:e[u]){
            dfs(v);
            ok[sg[v]]=1;
        }
        while (ok[sg[u]]){
            sg[u]++;
        }

        return sg[u];
    };

    int m;
    while (std::cin>>m){
        if (!m){
            return;
        }
        int ans=0;
        for (int i=1;i<=m;i++){
            int x;
            std::cin>>x;
            ans^=dfs(x);
        }
        if (ans){
            std::cout<<"WIN\n";
        }else{
            std::cout<<"LOSE\n";
        }
    }
}
int main(){
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout.tie(nullptr);

    int T=1;
    // std::cin>>T;
    while (T--){
        yrzr();
    }
    return 0;
}

HDU - 3595
Every-SG板子题,暴力打表贪心求最长/最短游戏步数(待交HDU)

#include <iostream>
#define ll long long
#define ull unsigned long long
int n;
int stp[1005][1005],sg[1005][1005];
int dfs(int x,int y){
    if (x<y){
        std::swap(x,y);
    }
    if (x==0||y==0){
        return 0;
    }
    if (stp[x][y]){
        return sg[x][y];
    }

    int minn=1e9,maxn=0;
    for (int i=y;i<=x;i+=y){
        if (!dfs(x-i,y)){
            maxn=std::max(maxn,stp[x-i][y]+1);
            sg[x][y]=1;
        }else{
            minn=std::min(minn,stp[x-i][y]+1);
        }
    }
    if (sg[x][y]){
        stp[x][y]=maxn;
    }else{
        stp[x][y]=minn;
    }
    return sg[x][y];
}
void yrzr(){
    int ans1=0,ans2=0;
    for (int i=1;i<=n;i++){
        int x,y;
        std::cin>>x>>y;
        if (x<y) std::swap(x,y);
        if (dfs(x,y)){
            ans1=std::max(ans1,stp[x][y]);
        }else{
            ans2=std::max(ans2,stp[x][y]);
        }
    }
    if (ans1>ans2){
        std::cout<<"MM\n";
    }else{
        std::cout<<"GG\n";
    }
}
int main(){
    while (std::cin>>n){
        // if (!n) return 0;
        yrzr();
    }
    return 0;
}

HDU - 1729
首先看到这种神奇的放石子方法,对于所有的 c c c,只要满足 c + c ∗ c > = s c+c*c>=s c+cc>=s,就可以看作经典的nim取石子, s g = s − c sg=s-c sg=sc
然后考虑 c + c ∗ c < s c+c*c<s c+cc<s的情况,能不能找到一种比较显然的先手必输的情况呢,我们发现,当 c c c是满足 c + c ∗ c < s c+c*c<s c+cc<s的最大 c c c时,先手必败,因为无论怎么走,下一步一定会转移到 c + c ∗ c > = s c+c*c>=s c+cc>=s的情况。
再考虑剩余 c + c ∗ c < s c+c*c<s c+cc<s的情况,假设满足条件的 t = m a x ( c ) t=max(c) t=max(c),可以将 ( s , c ) (s,c) (s,c) 转换为 ( t , c ) (t,c) (t,c),因为 t t t 是一个 s g = 0 sg=0 sg=0的先手必败态,等效于 s g s = 0 sg_s=0 sgs=0这个终止态

#include <bits/stdc++.h>
#define ll long long
#define ull unsigned long long
int dfs(int s,int c){
    int x=sqrt(1.0*s);
    while (x+x*x>=s){
        x--;
    }

    if (c>x){
        return s-c;
    }
    else if (c==x){
        return 0;
    }
    return dfs(x,c);
}
void yrzr(){
    int n,ans=0;
    std::cin>>n;
    for (int i=1;i<=n;i++){
        int s,c;
        std::cin>>s>>c;
        ans^=dfs(s,c);
    }
    if (ans){
        std::cout<<"Yes\n";
    }else{
        std::cout<<"No\n";
    }
}

翻硬币游戏结论:当前局面sg值为正面朝上硬币单独出现的异或和
1011=(1) ^ (001) ^ (0001)

树的删边游戏: A tree game
结论某个子树的根节点的 s g u sg_u sgu 为各孩子 ( s g v + 1 ) (sg_v+1) (sgv+1) 的异或和

拓展:如果变成图存在简单环:偶数环可看作一个点,奇数环看作一个新点和一条边

#include <bits/stdc++.h>
#define ll long long
#define ull unsigned long long
void yrzr(){
    int n;
    std::cin>>n;
    std::vector<std::vector<int>> e(n+1);
    for (int i=1;i<n;i++){
        int x,y;
        std::cin>>x>>y;
        e[x].push_back(y);
        e[y].push_back(x);
    }

    std::vector<int> sg(n+1);
    std::function<void(int,int)> dfs=[&](int u,int fa){
        for (auto v:e[u]){
            if (v==fa) continue;
            dfs(v,u);
            sg[u]^=sg[v]+1;
        }
    };
    dfs(1,0);
    if (sg[1]){
        std::cout<<"Alice\n";
    }else{
        std::cout<<"Bob\n";
    }
}

CF 1498F
树上阶梯博弈
根据阶梯博弈的结论,只有 ⌊ d e p u k ⌋ % 2 = 1 \lfloor {dep_u \over k} \rfloor \% 2=1 kdepu%2=1的节点会产生贡献,如果固定根,他们各点的异或和很好求,但是现在明显要用换根来计算各点作为根的贡献,首先我们考虑第一遍dfs的时候需要什么状态,比较显然的就是当 d e p u = 0 dep_u=0 depu=0时, u u u为根子树内所有节点 x x x,( ⌊ d e p x k ⌋ % 2 = 0 / 1 \lfloor {dep_x \over k} \rfloor \% 2=0/1 kdepx%2=0/1) 状态 0 / 1 0/1 0/1 的异或和,然后还要一个状态表示 d e p % k dep\%k dep%k的值,转移就很好想了。
最后换根

#include <bits/stdc++.h>
#define ll long long
#define ull unsigned long long
constexpr int N=1e5+5;
std::vector<int> e[N];
int a[N],f[N][25][2],n,k,temp[25][2];
void dfs1(int u,int fa){
    for (auto v:e[u]){
        if (v==fa) continue;
        dfs1(v,u);
    }
    f[u][0][0]=a[u];
    for (auto v:e[u]){
        if (v==fa) continue;
        for (int i=0;i<k-1;i++){
            for (int j=0;j<2;j++){
                f[u][i+1][j]^=f[v][i][j];
            }
        }
        f[u][0][1]^=f[v][k-1][0];
        f[u][0][0]^=f[v][k-1][1];
    }
}
void dfs2(int u,int fa){
    if (fa){
        for (int i=0;i<25;i++){
            for (int j=0;j<2;j++){
                temp[i][j]=f[fa][i][j];
            }
        }
        for (int i=0;i<k-1;i++){
            for (int j=0;j<2;j++){
                temp[i+1][j]^=f[u][i][j];
            }
        }
        temp[0][1]^=f[u][k-1][0];
        temp[0][0]^=f[u][k-1][1];

        for (int i=0;i<k-1;i++){
            for (int j=0;j<2;j++){
                f[u][i+1][j]^=temp[i][j];
            }
        }
        f[u][0][1]^=temp[k-1][0];
        f[u][0][0]^=temp[k-1][1];
    }
    for (auto v:e[u]){
        if (v==fa) continue;
        dfs2(v,u);
    }
}
void yrzr(){
    std::cin>>n>>k;
    for (int i=1;i<n;i++){
        int x,y;
        std::cin>>x>>y;
        e[x].push_back(y);
        e[y].push_back(x);
    }
    for (int i=1;i<=n;i++){
        std::cin>>a[i];
    }

    dfs1(1,0);
    dfs2(1,0);
    for (int i=1;i<=n;i++){
        int ans=0;
        for (int j=0;j<k;j++){
            ans^=f[i][j][1];
        }
        std::cout<<(ans>0)<<" ";
    }
}
int main(){
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout.tie(nullptr);

    int T=1;
    // std::cin>>T;
    while (T--){
        yrzr();
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值