20200611题解:树网的核
树网的核 洛谷P1099
这是一道非常修仙的题目。算法都很不正经。袁老师告诉我这是一道DP题,一看又是树上的题目,我就以为是树形dp,但是题解里没有树形DP。
看题解的时候我的心仿佛被放入了冰箱冷冻室的绿豆糕一样,因为我最怕的就是这种钻数据空子的不优美算法(因为这种算法的得分是看脸的)。在心态恢复后的第二天下午,我发现实际上它们只是看起来不优美;实际上,把性质分析完毕之后这道题就根本不是一道树形题目了,所以看起来才不对劲。我决定用一篇小论文的题解来把这道题消化。
树形DP
这是我的算法:
dp[u][l][0/1] = 以u为根节点,链的长度为l,能否往上走,在此子树中的最小偏心距。
前期都很顺利。但是当我要求dp[u][l][0]
的时候,发现无法求了。你不能直接拿它从dp[u][l - j][1]
和dp[v][j][1]
转移过来,因为dp[u][l][1]
的值是包括了来自v的子树到u的偏心距候选项的,而且没办法把它消掉。如果要消掉,就得再开一维k表示不考虑u的子树k提供的偏心距候选项。这样就超空间了,而且极其麻烦,不知道哪里又会埋藏着什么错误。
而且这个算法是没有用到题目提供的一些性质的(比如这条链肯定是在直径上)。所以肯定不对。
不过,在使用这个算法的过程中学会了求树的中心。
暴力
因为数据太弱,所以这个\(O(N^4)\)暴力居然可以过。
因为答案肯定是在直径上,所以做题者想出了一个办法。先用传统的dfs法搜出直径的其中一端,那么从这个点搜出去的所有路径肯定就会包括所有直径(因为直径有一条性质:在边权大于等于0的树上,所有直径的并集肯定是一棵树。用反证法可以证明)。然后,我们要找的路径就肯定是一条连接祖宗和孩子的路径了。枚举两个点i,j
使得j
是i
的祖先,再枚举路径上的所有点k
,再从每一个点k
深搜更新整棵树各个点到这条路径的最短距离,最后再扫描全图得到答案。
树的中心
用“求树的中心”的那一节的算法,可以“求出对于每个点i
,离它最远的点离它的距离“。
树的中心:设f(u)=离点u最远的点到点u的距离。f(u)最小的点就被称为树的中心。
首先任意指定一个根,这样才能树形DP。
f(u)要被分为两个部分,一个是u的子树内离u最远的(设为g(u)),一个是u的子树外离u最远的(设为h(u))。
子树内最远的很好解决;子树外最远的怎么做呢?
子树外最远的这个点到u的路径肯定经过u的父亲。所以,如果我们先求出了g(fa[u])和h(fa[u]),那么就可以用父亲的数据得到u的h(u)了。
但是,如果u正好是g(fa[u])所选择的那一个子树,就会导致边 <u,fa[u]>被来回走了两次。这当然是不合法的;所以我们还需要保存g(fa[u])所选择的是哪一个子树,并求出每个点的子树内离u第二远的(设为l(u))点到u的距离。
然后就可以F(u, 1, n) f[u] = max(g[u], h[u]);
了。
但是好像这个东西在这道题里并没有什么用。
证明:并不需要枚举每条直径,只需要任意一条(证明摘自题解)
那么如果一棵树有多条直径怎么办呢?没关系,任选一条即可。首先,两条直径不可能不相交。把相交的那一部分看成一个点,剩下的直径部分就会关于这个点对称。而如果选择的路径包含了分叉点,其偏心距就是恒定的(这个分叉点到直径末端的距离),所以可以任选一条直径求解。
优化
当我们找到了一条直径,这道题就变成了一道序列题。
当我们选择其中的一个子段时,这个子段的答案包括两部分:子段的两端到直径的两端有两个距离k1,k2
,以及每一个子段中的点往直径的两边搜索得到的答案f(u)
。因为不会重复搜到点,所以求出所有的f(u)
的时间是\(O(N)\)的。k1,k2
可以在dfs出整棵树的depth[]
之后\(O(1)\)求。所以,预处理出depth[]
和f(u)
后,这就真的成了一个滑动窗口的题目了。
单调队列可以O(N)解决。
所以 \(N \le 300\) 的数据范围完全可以再加大:据说BZOJ上有这道题的原题,数据范围是\(5\times10^5\)。