6.博弈

博弈

开题顺序: ACFJBDHIG

A HDU1524 A Chess Game

  • 多倍经验: LibreOJ 10243. 「一本通 6.7 例 3」移棋子游戏

  • 每个棋子之间相互独立,可以分开计算,最后再计算 Nim 和。

  • SG 函数暴力求 mex 值即可。

    点击查看代码
    int sg[1010],din[1010],n;
    vector<int>e[1010];
    void add(int u,int v)
    {
        e[u].push_back(v);
    }
    void dfs(int x)
    {
        bool vis[1010];
        memset(vis,0,sizeof(vis));
        for(int i=0;i<e[x].size();i++)
        {
            if(sg[e[x][i]]==-1)
            {
                dfs(e[x][i]);
            }
            vis[sg[e[x][i]]]=1;
        }
        for(int i=0;i<=n;i++)
        {
            if(vis[i]==0)
            {
                sg[x]=i;
                break;
            }
        }
    }
    int main()
    {
    // #define Isaac
    #ifdef Isaac
        freopen("in.in","r",stdin);
        freopen("out.out","w",stdout);
    #endif
        int m,x,sum=0,i,j;
        while(cin>>n)
        {
            for(i=0;i<=n-1;i++)
            {
                e[i].clear();
                din[i]=0;
                sg[i]=-1;
            }
            for(i=0;i<=n-1;i++)
            {
                cin>>m;
                for(j=1;j<=m;j++)
                {
                    cin>>x;
                    add(i,x);
                    din[x]++;
                }
            }
            for(i=0;i<=n-1;i++)
            {
                if(din[i]==0&&sg[i]==-1)
                {
                    dfs(i);
                }
            }
            while(cin>>m)
            {
                if(m==0)
                {
                    break;
                }
                else
                {
                    sum=0;
                    for(i=1;i<=m;i++)
                    {
                        cin>>x;
                        sum^=sg[x];
                    }
                    cout<<((sum==0)?"LOSE":"WIN")<<endl;
                }
            }
        }
        return 0;
    }
    

B [AGC016F] Games on DAG

  • 考虑计算有多少种生成子图满足 SG(1)=SG(2) ,然后用总方案数 2m 减去。

  • 观察到若 SG(u)=x ,则需要满足 y[0,x1],SG(v)=y,(u,v)E

  • 考虑按照 SG 函数值从大到小分层进行状压 DP 。设 f(S) 表示 SG 函数值 k 的所有点构成的集合为 S 时的合法方案数。

  • 转移时枚举 S 的子集 T 使得 TSG 函数值 =k 的点构成的集合, K=STSG 函数值 >k 的点构成的集合,且 1,2 同时属于 T 或同时不属于 T

    • 对于 KT 的边,每一个 S 中的元素都需要至少向 T 中连一条边,方案数为 xK(2yT[(x,y)E]1)
    • 对于 TK 的边,可以随便连,方案数为 2xK,yT[(y,x)E]
    • 对于 T 内部的边一条也不能连。
  • 预处理出每个点 x 向集合 S 中的点连边的数量,考虑新加入的 lowbit(S) 的贡献进行转移即可。

  • 时间复杂度为 O(3nn) ,貌似还可以做到 O(3n)

    点击查看代码
    const ll p=1000000007;
    ll mi[230],f[(1<<15)+10],cnt[17][(1<<15)+10],dis[17][17];
    ll lowbit(ll x)
    {
        return (x&(-x));
    }
    int main()
    {
    // #define Isaac
    #ifdef Isaac
        freopen("in.in","r",stdin);
        freopen("out.out","w",stdout);
    #endif
        ll n,m,u,v,mul,i,s,t,k;
        cin>>n>>m;
        mi[0]=1;
        for(i=1;i<=m;i++)
        {
            cin>>u>>v;
            u--;
            v--;
            dis[u][v]++;
            mi[i]=mi[i-1]*2%p;
        }
        for(s=0;s<(1<<n);s++)
        {
            for(i=0;i<n;i++)
            {
                cnt[i][s]=cnt[i][s^lowbit(s)];
                if(s!=0)
                {
                    cnt[i][s]+=dis[i][__builtin_ctz(s)];
                }
            }
        }
        f[0]=1;
        for(s=0;s<(1<<n);s++)
        {
            for(t=s;t!=0;t=s&(t-1))
            {
                k=s^t;
                mul=1;
                if((t&3)==0||(t&3)==3)
                {
                    for(i=0;i<n;i++)
                    {
                        if((k>>i)&1)
                        {
                            mul=mul*(mi[cnt[i][t]]-1)%p;
                        }
                        if((t>>i)&1)
                        {
                            mul=mul*mi[cnt[i][k]]%p;
                        }
                    }
                    f[s]=(f[s]+f[k]*mul%p)%p;
                }
            }
        }
        cout<<(mi[m]-f[(1<<n)-1]+p)%p;
        return 0;
    }
    

C [AGC014D] Black and White Tree

  • 判定胜负条件等价于是否存在一个白点不与任意一个黑点相邻,即存在一个白点只与白点相邻。

  • 发现染色顺序并不那么重要,我们只关注最终的状态及这个状态在博弈策略下能否合法。

  • 猜测能和 牛客周赛 Round 75 G 小红的双生树hard 一样通过钦定匹配状态判断是否合法。

  • 高桥染叶子一定不优,不妨自下而上进行考虑。高桥每次将一个叶子的父亲染白,青木只能将对应的叶子节点染黑。

  • 初始时若某个点有两个以上的儿子节点是叶子则高桥必胜,发现在上述染色过程中可以看做删子树(先前的已经被恰好匹配完)的过程,限制条件就变成了若某个点有两个以上未染色的儿子节点是删除对应节点后的“叶子”则高桥必胜。

  • 发现所求和 牛客周赛 Round 75 G 小红的双生树hard 一样,都是在判断原树是否有完美匹配。

    点击查看代码
    struct node
    {
        int nxt,to;
    }e[200010];
    int head[100010],col[100010],cnt=0,flag=0;
    void add(int u,int v)
    {
        cnt++;
        e[cnt].nxt=head[u];
        e[cnt].to=v;
        head[u]=cnt;
    }
    void dfs(int x,int fa)
    {
        for(int i=head[x];i!=0;i=e[i].nxt)
        {
            if(e[i].to!=fa)
            {
                dfs(e[i].to,x);
            }
        }
        if(col[x]==0)
        {
            if(col[fa]==0)
            {
                col[x]=col[fa]=1;
            }
            else
            {
                flag=1;
            }
        }
    }
    int main()
    {
    // #define Isaac
    #ifdef Isaac
        freopen("in.in","r",stdin);
        freopen("out.out","w",stdout);
    #endif	
        int n,u,v,i;
        cin>>n;
        for(i=1;i<=n;i++)
        {
            cin>>u>>v;
            add(u,v);
            add(v,u);
        }
        col[0]=1;
        dfs(1,0);
        cout<<((flag==1)?"First":"Second")<<endl;
        return 0;
    }
    

D CodeChef Destructive Nim

  • 考虑枚举 Ashish 先取的是哪一堆,此时游戏规则变成了先拿走若干个石子再选定对方选取的下一次的石子堆。

  • 考虑拿完当前这一堆的胜负情况。若自己拿完后必胜则直接赢了,否则拿到只剩一个后强迫对方也取这一堆,然后就赢了。

  • 故若当前这一堆大小 >1 ,可以看做直接把这一堆取完且不影响最终结果。

  • 若存在大小为 1 的石子,对方一定不会让自己先去取这堆石子,因为如果这样操作会导致交换了先后手一定不优。

    点击查看代码
    int main()
    {
    // #define Isaac
    #ifdef Isaac
        freopen("in.in","r",stdin);
        freopen("out.out","w",stdout);
    #endif
        int t,n,sum,x,i,j;
        cin>>t;
        for(j=1;j<=t;j++)
        {
            cin>>n;
            sum=0;
            for(i=1;i<=n;i++)
            {
                cin>>x;
                sum+=(x==1);
            }
            cout<<(((sum+(sum==n))%2==0)?"Utkarsh":"Ashish")<<endl;
        }
        return 0;
    }
    

E [AGC026F] Manju Game

F [AGC002E] Candy Piles

  • luogu P11231 [CSP-S 2024] 决斗 ,将 {a} 升序排序后将其抽象成二维平面上的网格。

  • 设当前在 (x,y) ,此时操作 1 转化成了从 (x,y) 走向 (x,y+1) ,操作 2 转化成了从 (x,y) 走向 (x+1,y) 。初始时在 (1,1)

  • fx,y 表示 (x,y) 局面时先手的胜负状态,初始时边界点的 f 值等于 0 ,转移有 fx,y=[fx+1,y=0][fx,y+1=0] 。特别地,对于不可能走到的点,我们定义其 f 值等于 0

  • 直接转移的时间复杂度为 O(nV) ,不可接受。

  • 分讨若干种情况后发现对于非边界点有 fi,j=fi+1,j+1

  • 代入 (1,1) 后找到极远的位置后处理上方和下方的距离取值得到胜负状态即可。

    点击查看代码
    int a[100010];
    int main()
    {
    // #define Isaac
    #ifdef Isaac
        freopen("in.in","r",stdin);
        freopen("out.out","w",stdout);
    #endif
        int n,i,j;
        cin>>n;
        for(i=1;i<=n;i++)
        {
            cin>>a[i];
        }
        sort(a+1,a+1+n,greater<int>());
        for(i=1;i<=n;i++)
        {
            if(a[i+1]<i+1)
            {
                for(j=0;a[i+1+j]==i;j++);
                cout<<(((j%2==1)||(a[i]-i)%2==1)?"First":"Second")<<endl;
                break;
            }
        }
        return 0;
    }
    

G [AGC005E] Sugigma: The Showdown

  • 不妨把树 a 和树 b 合并到同一张图上去考虑。

  • 若树 a 上一条路径 (u,v) 满足在树 bu,v 之间两点之间 3 ,则一旦 A 到达 u,v 其中一个点且后手这一回合没有抓住他就可以进行无限轮游戏。

  • 在追逐的过程中双方都不可能走回头路,否则不符合最优性的博弈策略。

  • 若仅考虑游戏在有限轮结束的情况,即树 a 任意上一条路径 (u,v) 在树 bu,v 之间两点之间都 2 的情况下,在以 y 为根时由于 A 无法跨越至 B 上方(否则就直接输了), A 任意时刻都在以 B 所在点为根的子树内。

  • 此时 A 到达的第 i 个点 ui 一定满足 disb(y,ui)>i ,否则就被追上了。根据此博弈策略 DFS 即可。

    • 追逐过程中停留下来的情况可以直接不管。
    点击查看代码
    struct node
    {
        int nxt,to;
    }e[400010];
    int head[200010],fa[200010],siz[200010],dep[200010],son[200010],top[200010],vis[200010],u[400010],v[400010],dis[200010],cnt=0,ans;
    vector<int>g[200010];
    void add(int u,int v)
    {
        cnt++;
        e[cnt].nxt=head[u];
        e[cnt].to=v;
        head[u]=cnt;
    }
    void dfs1(int x,int father)
    {
        siz[x]=1;
        fa[x]=father;
        for(int i=0;i<g[x].size();i++)
        {
            if(g[x][i]!=father)
            {
                dep[g[x][i]]=dep[x]+1;
                dfs1(g[x][i],x);
                siz[x]+=siz[g[x][i]];
                son[x]=(siz[g[x][i]]>siz[son[x]])?g[x][i]:son[x];
            }
        }
    }
    void dfs2(int x,int id)
    {
        top[x]=id;
        if(son[x]!=0)
        {
            dfs2(son[x],id);
            for(int i=0;i<g[x].size();i++)
            {
                if(g[x][i]!=fa[x]&&g[x][i]!=son[x])
                {
                    dfs2(g[x][i],g[x][i]);
                }
            }
        }
    }
    int lca(int u,int v)
    {
        while(top[u]!=top[v])
        {
            if(dep[top[u]]>dep[top[v]])
            {
                u=fa[top[u]];
            }
            else
            {
                v=fa[top[v]];
            }
        }
        return dep[u]<dep[v]?u:v;
    }
    bool check(int u,int v)
    {
        return dep[u]+dep[v]-2*dep[lca(u,v)]>=3;
    }
    void dfs(int x,int fa)
    {
        if(vis[x]==1)  ans=-1;
        if(ans!=-1)  ans=max(ans,2*dep[x]);
        for(int i=head[x];i!=0;i=e[i].nxt)
        {
            if(e[i].to!=fa)
            {
                dis[e[i].to]=dis[x]+1;
                if(dep[e[i].to]>dis[e[i].to])
                {
                    dfs(e[i].to,x);
                }
            }
        }
    }
    int main()
    {
    // #define Isaac
    #ifdef Isaac
        freopen("in.in","r",stdin);
        freopen("out.out","w",stdout);
    #endif	
        int n,x,y,i;
        cin>>n>>x>>y;
        for(i=1;i<=n-1;i++)
        {
            cin>>u[i]>>v[i];
            add(u[i],v[i]);
            add(v[i],u[i]);
        }
        for(i=1;i<=n-1;i++)
        {
            cin>>u[i+n]>>v[i+n];
            g[u[i+n]].push_back(v[i+n]);
            g[v[i+n]].push_back(u[i+n]);
        }
        dfs1(y,0);
        dfs2(y,y);
        for(i=1;i<=n-1;i++)
        {
            if(check(u[i],v[i])==true)
            {
                vis[u[i]]=vis[v[i]]=1;
            }
        }
        dfs(x,0);
        cout<<ans<<endl;
        return 0;
    }	
    
    

H [AGC010D] Decrementing

  • 任一时刻至少存在 1 个奇数。

  • 若没有操作 2 ,有 i=1n(ai1) 为奇数时先手必胜,否则后手必胜。

  • 观察到整体除以一个奇数并不影响 i=1n(ai1) 的奇偶性。若 i=1n(ai1) 为奇数,先手可以不断将偶数改成奇数使其奇偶性不变依此取得胜利。

  • 否则先手为了使 i=1n(ai1) 调整为偶数,只能选取唯一的奇数(有 1 存在时无效)让其变成偶数来除以一个偶数。若存在多个奇数先手无法均进行操作只能失败。

  • 顺次迭代即可。

    点击查看代码
    ll a[100010];
    ll gcd(ll a,ll b)
    {
        return b?gcd(b,a%b):a;
    }
    bool dfs(ll n)
    {
        ll sum=0,cnt=0;
        for(ll i=1;i<=n;i++)
        {
            sum+=a[i]-1;
            cnt+=(a[i]%2);
        }
        if(sum%2==1)  
        {
            return true;
        }
        if(cnt>=2)
        {
            return false;
        }
        for(ll i=1;i<=n;i++)
        {
            if(a[i]%2==1)
            {
                if(a[i]==1)
                {
                    return false;
                }
                a[i]--;
            }
        }
        sum=a[1];
        for(ll i=2;i<=n;i++)
        {
            sum=gcd(sum,a[i]);
        }
        for(ll i=1;i<=n;i++)
        {
            a[i]/=sum;
        }
        return dfs(n)^1;
    }
    int main()
    {	
    // #define Isaac
    #ifdef Isaac
        freopen("in.in","r",stdin);
        freopen("out.out","w",stdout);
    #endif
        ll n,i;
        cin>>n;
        for(i=1;i<=n;i++)
        {
            cin>>a[i];
        }
        cout<<(dfs(n)==true?"First":"Second")<<endl;
        return 0;
    }
    
    

I [AGC010E] Rearranging

  • 观察到在高桥操作完后,青木操作的过程中两个不互质的数其相对位置始终保持不变,考虑转化为 DAG 上的拓扑序。

  • ai,aj 不互质,则在它们之间连一条双向边。此时高桥需要做的是对于每个连通块内的边进行定向使其拓扑序最小,权值从小到大钦定连边形成森林即可。

  • 青木只需要用优先队列对整张图做一遍拓扑排序求解,写法同 luogu P3243 [HNOI2015] 菜肴制作

    点击查看代码
    int a[2010],din[2010],f[2010][2010],vis[2010],n;
    vector<int>e[2010];
    priority_queue<int>q;
    void add(int u,int v)
    {
        e[u].push_back(v);
    }
    int gcd(int a,int b)
    {
        return b?gcd(b,a%b):a;
    }
    void dfs(int x)
    {
        vis[x]=1;
        for(int i=1;i<=n;i++)
        {
            if(f[x][i]==1&&vis[i]==0)
            {
                add(x,i);
                din[i]++;
                dfs(i);
            }
        }
    }
    void top_sort()
    {
        for(int i=1;i<=n;i++)
        {
            if(din[i]==0)
            {
                q.push(i);
            }
        }
        while(q.empty()==0)
        {
            int x=q.top();
            q.pop();
            cout<<a[x]<<" ";
            for(int i=0;i<e[x].size();i++)
            {
                din[e[x][i]]--;
                if(din[e[x][i]]==0)
                {
                    q.push(e[x][i]);
                }
            }
        }
    }
    int main()
    {
    // #define Isaac
    #ifdef Isaac
        freopen("in.in","r",stdin);
        freopen("out.out","w",stdout);
    #endif
        int i,j;
        cin>>n;
        for(i=1;i<=n;i++)
        {
            cin>>a[i];
        }
        sort(a+1,a+1+n);
        for(i=1;i<=n;i++)
        {
            for(j=i+1;j<=n;j++)
            {
                if(gcd(a[i],a[j])!=1)
                {
                    f[i][j]=f[j][i]=1;
                }
            }
        }
        for(i=1;i<=n;i++)
        {
            if(vis[i]==0)
            {
                dfs(i);
            }
        }
        top_sort();
        return 0;
    }
    

J [AGC029D] Grid game

  • 高桥每一次都必须移动,否则青木也选择不动导致游戏结束。青木的移动策略是尽可能往满足横坐标大于纵坐标的障碍点的上方进行引导。

  • 暴力转移可行的移动范围的时间复杂度为 O(nm) ,无法接受,需要进一步优化。

  • 青木可行的移动范围被划分成了若干段区间,而这些区间中只有在横坐标大于纵坐标的障碍点的上方的点才有用。

  • 又因为我们只关注第一次不合法的点,不妨只维护每一行从 1 出发能到达的区间结尾。

  • 具体实现时并不需要对于每一行都重新计算前缀,而是可以记录 [1,i] 中能走到的最远的点,然后判断障碍点是否合法并取 min 即可。

  • 由反证法和最优性问题相关决策可知做法正确性。

    点击查看代码
    int pos[200010];
    pair<int,int>a[200010];
    map<pair<int,int>,bool>vis;
    int main()
    {
    // #define Isaac
    #ifdef Isaac
        freopen("in.in","r",stdin);
        freopen("out.out","w",stdout);
    #endif
        int n,m,k,ans,i;
        cin>>n>>m>>k;
        ans=n;
        for(i=1;i<=k;i++)
        {
            cin>>a[i].first>>a[i].second;
            vis[a[i]]=1;
        }
        for(i=1;i<=n;i++)
        {
            pos[i]=pos[i-1];
            pos[i]+=(pos[i]+1<=m&&vis.find(make_pair(i,pos[i]+1))==vis.end());
        }
        for(i=1;i<=k;i++)
        {
            if(pos[a[i].first-1]>=a[i].second)
            {
                ans=min(ans,a[i].first-1);
            }
        }
        cout<<ans<<endl;
        return 0;
    }
    

K luogu P2599 [ZJOI2009] 取石子游戏

L luogu P2490 [SDOI2011] 黑白棋

M luogu P6791 [SNOI2020] 取石子

posted @   hzoi_Shadow  阅读(23)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下
扩大
缩小
点击右上角即可分享
微信分享提示