Description 给你一个有 $n$ 个点的树,初始时有一个硬币在 1 号节点。现在玩家 A 和玩家 B 轮流操作。玩家 A 可以把一个点打叉,使硬币不能到达,B 可以把硬币移动一次,且硬币不能走重复路。游戏时双方都采用最优策略,现在问你:在玩家 A 不知道玩家 B 的操作的情况下是否存在一种打叉的策略使得硬币的移动次数小于 $k$。
Solution 个人比较喜欢这道题,感觉算是近些日子做的比赛中出得比较好的题了。
考虑转换问题,题目即求:在树中选择 $k$ 个深度不同的点(不能为根节点),使得以他们为根的子树包含了所有深度为 $k$ 的点,问你是否可行。
那么考虑在对树进行 DFS 遍历的时候把深度为 $k$ 的点记下来,为了方便表述,下文中称这个记录的顺序为 DFS 序。
再考虑证明只有当 $k^2 < n$ 时才有答案,一个感性的证明:
一个点两个分叉它容易被一石二鸟,不是很优秀。 所以一堆直链看起来最棒。 可以得出根号条直链最棒。
引用部分摘自 @WerKeyTom_FTD 的博客。
那么只有当 $k < 20$ 才可能有解,显然可以设 $f_{i, S} = 0 / 1$ 表示当前覆盖了 DFS 序中 $[1, i]$ 的点,打叉的点的深度集合为 $S$ 的这种局面 不可能 / 可能 出现。
具体的转移见 Code 部分。
Code 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 #include <cstdio> #include <cstring> #define maxN 410 struct edge { int x, y, g; } b[maxN << 1 ];int len = 0 , n = 0 , k = 0 ;bool f[maxN][1 << 20 ];int dfn[maxN], dep[maxN], end[maxN], fa[maxN], h[maxN];int max (int x, int y) { return x > y ? x : y; } void ins (int x, int y) { len++; b[len].x = x; b[len].y = y; b[len].g = h[x]; h[x] = len; } void dfs (int x) { for (int i = h[x];i;i = b[i].g) { int y = b[i].y; if (dep[y] < 0 ) { fa[y] = x; dep[y] = dep[x] + 1 ; if (dep[y] == k) { end[y] = ++dfn[0 ]; dfn[dfn[0 ]] = y; } dfs(y); end[x] = max(end[x], end[y]); } } } int main () { scanf ("%d %d" , &n, &k); if (k * k >= n) { printf ("DA" ); return 0 ; } for (int i = 1 ;i < n; i++) { int x = 0 , y = 0 ; scanf ("%d %d" , &x, &y); ins(x, y); ins(y, x); } int root = 1 ; memset (dep, -127 / 3 , sizeof (dep)); dep[root] = 0 ; dfs(root); f[0 ][0 ] = true ; int Ma = (1 << k) - 1 ; for (int i = 0 ;i < dfn[0 ]; i++) { for (int S = 0 ;S <= Ma; S++) { if (f[i][S]) { int x = dfn[i + 1 ]; while (dep[x]) { if (!(S & (1 << (dep[x] - 1 )))) { f[end[x]][S | (1 << (dep[x] - 1 ))] = true ; } x = fa[x]; } } } } printf ("%s" , f[dfn[0 ]][Ma] ? "DA" : "NE" ); return 0 ; }