Codeforces 919F SG大爆搜(拓扑)

本文详细解析了一款基于卡牌的博弈游戏算法。通过压缩状态和建立有向图的方式,运用SG函数进行搜索,实现了对游戏结果的有效预测。文章深入探讨了算法的设计思路和技术细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意:臭名昭著的Alice和Bob又要玩游戏 :(

游戏规则:每个人有八张卡牌,卡牌的数字可以是0、1、2、3、4,在一个人的回合中,它可以选择自己一张牌,再选择对手一张牌,然后把选中的自己这个牌,替换成两张牌之和%5,对手的牌不变。率先实现八张零牌的人获胜。保证先手的人不会立即获胜,存在平局情况,数据有10W组。


题解:显然这个数据量要爆搜打表O1出答案。此题无非就是一个单纯的有向图博弈,因此需要做的就是想帮法把当前局面压缩起来当作点,然后用局面的转移关系建图连边,然后拓扑地依据SG函数的转移条件(所有出态局面都是先手胜->本局面先手负;存在一个出态局面先手负->本局面先手胜)大爆搜即可。

注意到一个人卡牌的顺序是无意义的,因此可以把一个人的状态用5个数字(每种手牌分别有几个,低位代表小数字)表示。我的处理是用一个5位的10进制数字压缩一个人的局面(虽然可以9进制,但是10进制Debug容易),先dfs把总共495种手牌情况搜出来,然后再495^2组合出每一种局面情况并做离散化对应到1-495^2个点上,然后5^2(枚举两个人牌值)地对每个点建立边(反向)。然后就可以拓扑了。数组范围都是打表打出来的,我没学过数学,不会算 :(

这个破题我写了3h。。。第一次写SG爆搜。。。好多东西写这些着就晕了。。。总之就是各种问题。。。人都写傻了。。。菜啊。。。最后跑了700ms还好还好。。。

Code:

#include<bits/stdc++.h>
using namespace std;
const int maxn = 500;
int d[maxn*maxn];
map<int,int>id;int idcnt;
map<pair<int,int>,int>id2;int id2cnt;
pair<int,int> status[maxn*maxn];
int ans[maxn*maxn];
int first[maxn*maxn],nxt[1750000],des[1750000],tot;
int W[6];
int T,f;
queue<int>Q;
inline int get(int status,int x_){
    return status%W[x_+1]/W[x_];
}
void dfs(int dep,int rest,int val){
    if (dep==4){
        int temp = val+rest*W[4];
        if (!id.count(temp)){
            id.insert(make_pair(temp,++idcnt));
        }
        return;
    }
    for (int i=0;i<=rest;i++){
        dfs(dep+1,rest-i,val+W[dep]*i);
    }
}
inline void AE(int id1,int id2){
    tot++;
    des[tot] =id2;
    nxt[tot] = first[id1];
    first[id1] = tot;
    d[id2]++;
}
void addEdge(int fi,int se,int idd){
    for (int i=1;i<=4;i++){
        for (int j=1;j<=4;j++){
            int f = get(fi,i);
            int s = get(se,j);
            if (f>0&&s>0){
                AE(id2[make_pair(se,fi-W[i]+W[(i+j)%5])],idd);
            }
        }
    }
}
void search(){
    for (int i=1;i<=id2cnt;i++){
        if (d[i]==0){
            if (status[i].first==8){
                ans[i] =1;
            }else{
                ans[i] =-1;
            }
            Q.push(i);
        }
    }
    while (!Q.empty()){
        int q = Q.front();
        Q.pop();
        if (ans[q]==1){
            for (int t = first[q];t;t=nxt[t]){
                int v = des[t];
                d[v]--;
                if (d[v]==0&&ans[v]==0){
                    ans[v]=-1;
                    Q.push(v);
                }

            }
        }else{
            for (int t = first[q];t;t=nxt[t]){
                int v = des[t];
                d[v]--;
                if (ans[v]==0){
                    ans[v]=1;
                    Q.push(v);
                }
            }
        }
    }
}
void init(){
    W[0]=1;
    for (int i=1;i<=5;i++)W[i] =W[i-1]*10;
    dfs(0,8,0);
    for (pair<int,int>fi :id){
        for (pair<int,int>se:id){
            id2.insert(make_pair(make_pair(fi.first,se.first),++id2cnt));
            status[id2cnt] = make_pair(fi.first,se.first);
        }
    }
    memset(d,0,sizeof d);
    for (pair<pair<int,int>,int>e:id2){
        addEdge(e.first.first,e.first.second,e.second);
    }
    search();
}
void solve(){
    scanf("%d",&f);
    int ast=0,bst=0;
    for (int i=0;i<8;i++){
        int t;
        scanf("%d",&t);
        ast+=W[t];
    }
    for (int i=0;i<8;i++){
        int t;
        scanf("%d",&t);
        bst+=W[t];
    }
    if (f){
        int res = ans[id2[make_pair(bst,ast)]];
        if (res==1){
            printf("Bob\n");
        }else if (res==-1){
            printf("Alice\n");
        }else{
            printf("Deal\n");
        }
    }else{
        int res = ans[id2[make_pair(ast,bst)]];
        if (res==1){
            printf("Alice\n");
        }else if (res==-1){
            printf("Bob\n");
        }else{
            printf("Deal\n");
        }
    }
}
int main(){
    init();
    scanf("%d",&T);
    while (T--){
        solve();
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值