移棋子游戏

发布时间 2023-08-08 12:23:35作者: nyliyuzhong

给定一个有 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
# 题解

 

 

 

 # 代码

```

#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;
}

```