给定一个有 N 个节点的有向无环图,图中某些节点上有棋子,两名玩家交替移动棋子。
玩家每一步可将任意一颗棋子沿一条有向边移动到另一个点,无法移动者输掉游戏。
对于给定的图和棋子初始位置,双方都会采取最优的行动,询问先手必胜还是先手必败。
输入格式
第一行,三个整数N,M,K,N 表示图中节点总数,M 表示图中边的条数,K 表示棋子的个数。
接下来 M 行,每行两个整数 X,Y 表示有一条边从点 X 出发指向点 Y。
接下来一行, K 个空格间隔的整数,表示初始时,棋子所在的节点编号。
节点编号从 1 到 N。
输出格式
若先手胜,输出 win,否则输出 lose。
数据范围
1≤N≤2000,
1≤M≤6000,
1≤K≤N
输入样例:
6 8 4 2 1 2 4 1 4 1 5 4 5 1 3 3 5 3 6 1 2 4 6
输出样例:
win
# 题解
## 思路: 求出所有棋子的 sg 函数值的异或值,若为0,则先手必败,否则先手必胜
# 代码
```
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <set>
using namespace std;
const int N = 2005;
const int M = 6005;
int n, m, k;
int h[N], e[M], ne[M], idx; // 邻接表存图
int sg[N]; // 存所有被计算过的点的 SG 函数值
int res;
inline void add(int u, int v) // 加边函数。从点 u 向点 v 连一条有向边
{
e[ ++ idx] = v;
ne[idx] = h[u];
h[u] = idx;
}
int SG(int u)
{
if (~sg[u]) return sg[u]; // 如果当前 sg[u] 不是 -1,那么说明该点的 SG 函数值已经被计算过了,直接返回
set<int> S; // 否则要建一个集合 S,存该点能到的所有点的 SG 函数值
for (int i = h[u]; i; i = ne[i]) // 遍历点 u 能到达的所有点
S.insert(SG(e[i])); // 计算该点的 SG 函数值,并放入集合 S
for (int i = 0; ; i ++ ) // 从 0 开始枚举所有非负整数
if (!S.count(i)) // 如果该值没有在 S 中出现过
{
sg[u] = i; // 那么将该值记录在 sg[u] 中并返回
return i;
}
}
int main()
{
scanf("%d %d %d", &n, &m, &k); // 读入题目中 N, M, K
for (int i = 0; i < m; i ++ ) // 读入 M 条边并建图
{
int u, v;
scanf("%d %d", &u, &v);
add(u, v);
}
memset(sg, -1, sizeof sg); // 先将 sg 数组中的所有值初始化成 -1,表示没有记录过
while (k -- ) // 读入 K 个棋子所在的点
{
int u;
scanf("%d", &u);
res ^= SG(u);
}
if (res) puts("win"); // 如果 res 不为 0,那么输出 win
else puts("lose"); // 否则输出 lose
return 0;
}
```
标签:游戏,int,sg,博弈论,棋子,res,include,SG From: https://www.cnblogs.com/liyuzhong/p/17613854.html