##[$AcWing$ $1319$. 移棋子游戏](https://www.acwing.com/problem/content/description/1321/) ### 一、题目描述 给定一个有 $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$ **输入样例:** ```cpp {.line-numbers} 6 8 4 2 1 2 4 1 4 1 5 4 5 1 3 3 5 3 6 1 2 4 6 ``` **输出样例:** ```cpp {.line-numbers} 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` ### 三、实现代码 ```cpp {.line-numbers} #include 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 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; } ```