1007 童年

题解:博弈论与拓扑排序的应用

题目描述

小 A 和小 B 玩一个游戏,每人伸出两只手,每只手表示 1 到 9 的某个数字。两人轮流行动,每次选择自己的一只手去碰对方的一只手,之后自己的这只手的数字变为二者相加对 10 取模。当某只手的数字变为 0 时,可以将这只手收回。如果一方的两只手都收回,则该方获胜。

给定初始状态 (ax, ay, bx, by),其中 axay 表示 A 的两只手上的数字,bxby 表示 B 的两只手上的数字。判断在双方都足够聪明的情况下:

  • 1 表示 A 必胜。
  • 2 表示 B 必胜。
  • 0 表示游戏会陷入循环(平局)。

输入格式

第一行一个正整数 $ T $ 表示测试点个数。

后面 $ T $ 行,每行四个正整数 $ ax, ay, bx, by $ 表示当前局面。

输出格式

对于每个局面,输出一个整数,表示结果为 120


算法思路

1. 博弈论中的状态建模

我们可以将游戏的所有可能状态建模为一个有向图:

  • 每个节点表示一个状态 (ax, ay, bx, by),其中 ax, ay 是 A 的两只手上的数字,bx, by 是 B 的两只手上的数字。
  • 每条边表示一次合法的操作,从当前状态转移到下一个状态。

例如:

  • 如果 A 的一只手 ax 碰到 B 的一只手 bx,则新的状态为 ((ax + bx) % 10, ay, bx, by)

2. 初始状态的结果

根据规则:

  • 如果 A 的两只手都为 0(即 ax == 0 && ay == 0),则 A 胜,结果为 1(A 必胜)。
  • 如果 B 的两只手都为 0(即 bx == 0 && by == 0),则 B 胜,结果为 2(B 必胜)。

这些状态是已知的,相当于拓扑排序中的“入度为 0”的节点。

3. 状态转移与推导

对于其他状态,我们可以通过以下规则推导其结果:

  • 必胜态:如果一个状态能够转移到必败态,则它是必胜态。
  • 必败态:如果一个状态只能转移到必胜态,则它是必败态。
  • 平局态:如果既不能确定为必胜态,也不能确定为必败态,则它是平局态。

这种推导过程类似于拓扑排序的思想:

  • 我们从已知的必胜或必败状态开始,逐步推导其他状态的结果。
  • 使用反向图(reverse_G)来快速找到哪些状态可以转移到当前状态。

4. 拓扑排序实现

我们使用队列来实现拓扑排序:

  1. 初始化队列,将所有已知结果的状态(必胜或必败)加入队列。
  2. 每次从队列中取出一个状态 $ u $,根据其结果更新前驱状态:
    • 如果 $ u $ 是必败态,则所有能转移到 $ u $ 的状态都是必胜态。
    • 如果 $ u $ 是必胜态,则检查其前驱状态是否满足必败态的条件(即入度为 0)。
  3. 更新完成后,将新的状态加入队列。
  4. 最终,未被处理的状态为平局态。

代码详解

1. 数据结构

  • 状态定义

    struct node {
        int ax, ay, bx, by; // A 的两只手和 B 的两只手的状态
    };
    

    每个状态由 ax, ay, bx, by 四个数字表示。

  • 状态 ID 映射表

    int id[10][10][10][10];
    

    使用四维数组 id 将每个状态映射到唯一的 ID,方便后续处理。

  • 图的表示

    vector<int> G[N];         // 正向图
    vector<int> reverse_G[N]; // 反向图
    
  • 结果数组

    int result[N]; // 每个状态的结果(0: 平局, 1: A 必胜, 2: B 必胜)
    

2. 初始化状态

void init() {
    queue<int> q;
    for (int ax = 0; ax <= 9; ax++) {
        for (int ay = ax; ay <= 9; ay++) {
            for (int bx = 0; bx <= 9; bx++) {
                for (int by = bx; by <= 9; by++) {
                    a[++top] = node{ax, ay, bx, by};
                    id[ax][ay][bx][by] = top;
                    if (ax == 0 && ay == 0) result[top] = 1, q.push(top);
                    else if (bx == 0 && by == 0) result[top] = 2, q.push(top);
                }
            }
        }
    }
}
  • 遍历所有可能的状态 (ax, ay, bx, by),并将其存储在数组 a 中。
  • 对于初始状态(A 或 B 的两只手都为 0),直接标记结果,并加入队列。

3. 构建状态转移图

for (int ax = 0; ax <= 9; ax++) {
    for (int ay = max(1, ax); ay <= 9; ay++) {
        for (int bx = 0; bx <= 9; bx++) {
            for (int by = max(bx, 1); by <= 9; by++) {
                int u = id[ax][ay][bx][by];
                if (ax != 0) {
                    if (bx != 0) add(u, (ax + bx) % 10, ay, bx, by);
                    add(u, (ax + by) % 10, ay, bx, by);
                }
                if (bx != 0) {
                    add(u, (ay + bx) % 10, ax, bx, by);
                }
                add(u, (ay + by) % 10, ax, bx, by);
            }
        }
    }
}
  • 根据游戏规则,构建状态之间的转移关系。
  • 使用函数 add 添加边到正向图和反向图。

4. 拓扑排序

while (!q.empty()) {
    int u = q.front();
    q.pop();
    int op = result[u];
    if (op == 1) {
        for (auto v : reverse_G[u]) if (result[v] == 0) {
            cnt[v]++;
            if (cnt[v] == G[v].size()) result[v] = 2, q.push(v);
        }
    } else {
        for (auto v : reverse_G[u]) {
            if (result[v] == 0) {
                result[v] = 1, q.push(v);
            }
        }
    }
}
  • 从队列中取出状态 $ u $,根据其结果更新前驱状态。
  • 使用反向图 reverse_G 找到所有能转移到 $ u $ 的状态,并更新其结果。

5. 查询结果

void solve() {
    int ax, ay, bx, by;
    cin >> ax >> ay >> bx >> by;
    if (ax > ay) swap(ax, ay);
    if (bx > by) swap(bx, by);
    cout << result[id[ax][ay][bx][by]] << '\n';
}
  • 对于每个查询,确保输入的手牌按从小到大的顺序排列。
  • 输出对应状态的结果。
点击查看代码
#include<bits/stdc++.h>
// #define int long long
#define all(x) x.begin(),x.end()
#define rall(x) x.rbegin(),x.rend()
#define pb push_back
#define pii pair<int,int>
using namespace std;
const int mod=998244353;
int gcd(int a,int b){return b?gcd(b,a%b):a;};
int qpw(int a,int b){int ans=1;while(b){if(b&1)ans=ans*a%mod;a=a*a%mod,b>>=1;}return ans;}
int inv(int x){return qpw(x,mod-2);}
const int N=1e5+10;
struct node {
    int ax,ay,bx,by;
};
vector<int>G[N];
vector<int>reverse_G[N];
int id[10][10][10][10];
node a[N];
int result[N];
int cnt[N];
int top=0;
void add(int u,int ax,int ay,int bx,int by) {
    if (ax>ay)swap(ax,ay);
    int v=id[bx][by][ax][ay];
    G[u].pb(v);
    reverse_G[v].pb(u);
}
void init() {
    queue<int>q;
    for (int ax=0;ax<=9;ax++) {
        for (int ay=ax;ay<=9;ay++) {
            for (int bx=0;bx<=9;bx++) {
                for (int by=bx;by<=9;by++) {
                    a[++top]=node{ax,ay,bx,by};
                    id[ax][ay][bx][by]=top;
                    if (ax==0&&ay==0)result[top]=1,q.push(top);
                    else if (bx==0&&by==0)result[top]=2,q.push(top);
                }
            }
        }
    }
    // int ocnt=0;
    for (int ax=0;ax<=9;ax++) {
        for (int ay=max(1,ax);ay<=9;ay++) {
            for (int bx=0;bx<=9;bx++) {
                for (int by=max(bx,1);by<=9;by++) {
                    int u=id[ax][ay][bx][by];
                    if (ax!=0) {
                        if (bx!=0) {
                            add(u,(ax+bx)%10,ay,bx,by);
                        }
                        add(u,(ax+by)%10,ay,bx,by);
                    }
                    if (bx!=0) {
                        add(u,(ay+bx)%10,ax,bx,by);
                    }
                    add(u,(ay+by)%10,ax,bx,by);
                }
            }
        }
    }
    // cout<<ocnt<<endl;
    while (!q.empty()) {
        int u=q.front();
        q.pop();
        int op=result[u];
        if (op==1) {
            for (auto v:reverse_G[u])if (result[v]==0) {
                cnt[v]++;
                if (cnt[v]==G[v].size())result[v]=2,q.push(v);
            }
        }else {
            for (auto v:reverse_G[u]) {
                if (result[v]==0) {
                    result[v]=1,q.push(v);
                }
            }
        }
    }
}
void solve(){
    int ax,ay,bx,by;
    cin>>ax>>ay>>bx>>by;
    if (ax>ay)swap(ax,ay);
    if (bx>by)swap(bx,by);
    cout<<result[id[ax][ay][bx][by]]<<'\n';
}
signed main(){
    init();
    ios::sync_with_stdio(false),cin.tie(nullptr),cout.tie(nullptr);
    int _=1;
    cin>>_;
    while(_--)solve();
}
/*
1
0 9 1 1
1
*/
posted @ 2025-03-29 15:43  archer2333  阅读(103)  评论(3)    收藏  举报