题目链接:https://codeforces.com/contest/1467/problem/E
观察
对于某种权值 (val) ,若点 (i) 的权值 (a[i]=val) 则把这个点染成黑色,其他的点染成白色。
定义黑色节点的数量,为权值 (val) 的出现次数 (cnt[val]) 。
若以点 (r) 为根节点进行先序遍历,定义从点 (r) 到点 (u) 经过的黑色节点的数量,为权值 (val) 的深度 (dep[val][u]) ,记出现的最大深度为 (mdp[val]) 。
情形A:(cnt[val]=1) ,说明权值 (val) 不影响答案。
情形B:(cnt[val]geq2,mdp[val]=1) ,说明删除所有的黑色节点后,合法的答案必须和根节点 (r) 处在同一连通块中。
情形C:(cnt[val]geq2,mdp[val]=2) ,情况非常复杂。
情形D:(cnt[val]geq2,mdp[val]geq3) ,说明权值 (val) 直接导致答案无解。
算法流程
-
以点 (1) 为根节点,先序遍历这棵树,记录以下信息:
每一种权值的出现次数 (cnt[i])
每一种权值的深度 (dep[i])
每一种权值的最大深度 (mdp[i]) -
以点 (1) 为根节点,再次先序遍历这棵树,记录以下信息:
假如这种权值的出现次数 (cnt[i]=1) ,那么直接忽略这种权值,结束。
假如这种权值的最大深度 (mdp[i]=1) ,那么给这种权值的所有点都打上 (down) 标记,结束。
那么这种权值的最大深度 (mdp[i]geq2),那么给这种权值的所有点都打上 (down) 标记,给这种权值的所有 (dep[i]geq2) 的点的前一个点打上 (dirup) 标记,结束。 -
记某个点为 (u) ,记点 (u) 的父节点为点 (p) ,则:
(down[u]) 标记 表示以点 (1) 为整棵树的根节点时,以点 (u) 为根节点的整个连通块都是非法的。
(dirup[u]) 标记 表示以点 (u) 为整棵树的根节点时,以点 (p) 为根节点的整个连通块都是非法的。
假如 (dirup[u]=cnt[a[p]]-1) ,则说明以点 (u) 为整棵树的根节点时,权值 (a[p]) 的分布恰好为情形B,所以要清除掉点 (p) 的 (down) 标记。 -
下推所有 (down) 标记,上推所有的 (dirup) 标记,统计答案。
总结
其中,要计算“带有方向的up标记”的方向,目前没有想到更好的办法,使用的是类似倍增lca的倍增算法计算出方向。总体时间复杂度 (O(nlogn)) 。
一道对我来说非常难的“树”论问题,也通过这一题感觉到自己确实比起9个月前强大了,无论是从代码能力、逻辑分析能力、或者是恒心和耐心都是。