3.4 KiB
一、题目描述
给定一个有 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
二、解题思路
首先定义 mex
函数,这是施加于一个集合的函数,返回 最小的不属于这个集合的非负整数
例:mex({1,2})=0,mex({0,1})=2,mex({0,1,2,4})=3
在一张有向无环图中,对于每个点 u
,设其 所有能到的点 的 SG
函数值集合为集合 A
,那么 u
的 SG
函数值为 mex(A)
,记做 SG(u)=mex(A)
如图:

例图解释:
SG(5)=mex({\phi})=0
SG(3)=mex({SG(5)})=mex({0})=1
SG(4)=mex({SG(5),SG(3)})=mex({0,1})=2
SG(2)=mex({SG(3)})=mex({1})=0
SG(1)=mex({SG(2),SG(4)})=mex({0,2})=1
本题思路
-
如果只有一个棋子(棋子位置是
s
): 先手必胜\Leftrightarrow
sg(s)!=0
-
存在多个棋子(其实可以看成存在多个相同的棋盘,棋子的位置是
s_1,…,s_k
先手必胜\Leftrightarrow
sg(s1)^sg(s2)^...^sg(sk) != 0
三、实现代码
#include <bits/stdc++.h>
using namespace std;
const int N = 2010, M = 6010;
// SG函数模板题
int n, m, k;
int f[N];
int h[N], e[M], ne[M], idx;
void add(int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
int sg(int u) {
//记忆化搜索
if (~f[u]) return f[u];
//找出当前结点u的所有出边,看看哪个sg值没有使用过
set<int> S;
for (int i = h[u]; ~i; i = ne[i])
S.insert(sg(e[i]));
//找到第一个没有出现的过的自然数, 0,1,2,3,4,...
for (int i = 0;; i++)
if (S.count(i) == 0) {
f[u] = i;
break;
}
return f[u];
}
int main() {
memset(h, -1, sizeof h);
cin >> n >> m >> k;
while (m--) {
int a, b;
cin >> a >> b;
add(a, b);
}
memset(f, -1, sizeof f); //初始化sg函数的结果表
int res = 0;
while (k--) {
int u;
cin >> u;
res ^= sg(u); //计算每个出发点的sg(u),然后异或在一起
}
if (res) //所有出发点的异或和不等于0,先手必胜
puts("win");
else //所有出发点的异或和等于0,先手必败
puts("lose");
return 0;
}