CF457F 题解

大体思路

由于某个节点的操作取决于子树内的操作次数,我们定义“奇子树”为一个操作完整个子树的次数为奇数的子树,“偶子树“类似。

首先有一个很经典的 trick:采用二分,凡是大于等于判定值的赋为 $1$,否则为 $0$,则问题变成了值只有 $0,1$ 的问题。

如果一棵树是“奇子树”,则先手能进行最后一步操作,此时两个子树中只要有一个 $1$ 先手即可获胜。如果一棵树是“偶子树”的话,后手进行最后一步操作,仅当两个子树均为 $1$ 时先手才能获胜。

最简单的情况是一个节点的两个子树均为“偶子树”。此时如果后手在先手必胜的子树内操作,先手有必胜策略,否则先手随便选即可保证自己的胜利。

但是当一个节点的两个子树均为“奇子树“时,我们发现这个策略不管用了。因为先手无法通过跟着后手选来消耗步数,维持自己的必胜态。此时仅当先手”停一步“后仍能胜利才能获得最终的胜利。

因此采用 DP:$f_{u,(0/1),(0/1/2)}$ 表示在 $u$ 子树内,当前是先/后手,两个儿子的状态是两个偶数/两个奇数/一奇一偶的答案。

分类讨论转移

接下来到了喜闻乐见的大分讨环节。首先把两个儿子都是叶子的情况特判掉。由于当前为后手的情况完全等同于当前为先手情况的相反操作,我们只考虑当前为先手的情况。

  • 两个子树均为“偶子树”:先手赢得任意子树即可;

  • 两个子树均为“奇子树”:先手在其中一个子树停一手获胜,并在另一子树获胜;

  • 一个子树为“奇子树”,另一个为“偶子树”:先手赢得奇子树,后手无法赢得偶子树或先手在偶子树“停一手”获胜,后手无法赢得奇子树。

这样这道题就做完了。

代码实现

看上去也不是很困难,对吗?放一下 DP 转移的代码:

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
if(sz[rid]&1)swap(lid,rid);
if(!(sz[u]&1)){
f[u][0][0]=(f[lid][0][0]&&f[rid][1][0])||(f[lid][1][1]&&f[rid][0][1]);
f[u][1][0]=(f[lid][1][0]||f[rid][0][0])&&(f[lid][0][1]||f[rid][1][1]);
for(int i=1;i<=2;i++){
f[u][0][i]=f[lid][0][(!sz[rid]&&i==1)?2:1]||f[rid][0][0];
f[u][1][i]=f[lid][1][(!sz[rid]&&i==1)?2:1]&&f[rid][1][0];
}
}
else {
f[u][0][0]=f[lid][0][sz[lid]&1]||f[rid][0][sz[rid]&1];
f[u][1][0]=f[lid][1][sz[lid]&1]&&f[rid][1][sz[rid]&1];
for(int i=1;i<=2;i++){
f[u][0][i]=f[u][1][0],f[u][1][i]=f[u][0][0];
if(sz[lid]&1){
f[u][0][i]|=(f[lid][0][0]&&f[rid][1][(sz[lid]==1&&i==1)?2:1])||(f[lid][1][(sz[rid]==1&&i==1)?2:1]&&f[rid][0][0]);
f[u][1][i]&=(f[lid][1][0]||f[rid][0][(sz[lid]==1&&i==1)?2:1])&&(f[lid][0][(sz[rid]==1&&i==1)?2:1]||f[rid][1][0]);
}
else{
if(sz[lid]){
f[u][0][i]|=f[lid][0][(!sz[rid]&&i==1)?2:1]&&f[rid][1][0];
f[u][1][i]&=f[lid][1][(!sz[rid]&&i==1)?2:1]||f[rid][0][0];
}
if(sz[rid]){
f[u][0][i]|=f[lid][1][0]&&f[rid][0][(!sz[lid]&&i==1)?2:1];
f[u][1][i]&=f[lid][0][0]||f[rid][1][(!sz[lid]&&i==1)?2:1];
}
}
}
}

对了,实际实现的时候,可以把 $0,1$ 序列的操作直接变成取最小/最大值,从而省去一次二分。