1007 童年
题解:博弈论与拓扑排序的应用
题目描述
小 A 和小 B 玩一个游戏,每人伸出两只手,每只手表示 1 到 9 的某个数字。两人轮流行动,每次选择自己的一只手去碰对方的一只手,之后自己的这只手的数字变为二者相加对 10 取模。当某只手的数字变为 0 时,可以将这只手收回。如果一方的两只手都收回,则该方获胜。
给定初始状态 (ax, ay, bx, by),其中 ax 和 ay 表示 A 的两只手上的数字,bx 和 by 表示 B 的两只手上的数字。判断在双方都足够聪明的情况下:
1表示 A 必胜。2表示 B 必胜。0表示游戏会陷入循环(平局)。
输入格式
第一行一个正整数 $ T $ 表示测试点个数。
后面 $ T $ 行,每行四个正整数 $ ax, ay, bx, by $ 表示当前局面。
输出格式
对于每个局面,输出一个整数,表示结果为 1、2 或 0。
算法思路
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. 拓扑排序实现
我们使用队列来实现拓扑排序:
- 初始化队列,将所有已知结果的状态(必胜或必败)加入队列。
- 每次从队列中取出一个状态 $ u $,根据其结果更新前驱状态:
- 如果 $ u $ 是必败态,则所有能转移到 $ u $ 的状态都是必胜态。
- 如果 $ u $ 是必胜态,则检查其前驱状态是否满足必败态的条件(即入度为 0)。
- 更新完成后,将新的状态加入队列。
- 最终,未被处理的状态为平局态。
代码详解
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
*/

浙公网安备 33010602011771号