D. The Omnipotent Monster Killer
题目大意:
有一棵树,树节点数不超过\(3·10^5\),每个节点的权值,定义为数组\(a(a_i<10^{12})\),初始\(sum=0\),每一轮执行如下操作:
- 计算当前剩余所有的点权和,累计到\(sum\)中
- 任选若干个互不相邻的节点并移除
重复这个操作直到树上没有任何节点为止,最小化\(sum\)。
整体感觉这道题带来的启发很多,值得慢慢细品。
首先第一个误区以为可以贪心的两轮操作结束,比如:
先移除2 4,再移除1 3。
但是很容易举出反例:
显然要先移除4 5,再移除一个2,最后移除剩余的1,需要三轮才能完成。
三轮能否确定结束呢?如果是链式结构确实是可以的,因为链式结构中,任意节点最多只有两个节点相邻,这三者互不相同也只有三轮操作。
而在树上,某个节点N相邻可能有n个\(X_i\),如果每一个均在不同轮次移除,那么节点N需要在第\(n+1\)轮才能移除。
然后是第二个误区,是否每轮选择树上最大独立集?即任选互不相邻节点,树形dp求权重和最大的选择。
这个可以解决上面1-2-3-4和5-2-1-4两种情况。
如果有多轮,似乎也可以解决,然而这也是不对的,简单反例如下:
第一轮最大独立集是(5,6),然而剩余的(3,4)由于是相邻的,只能先选4再选3,\(sum=(6+5+4+3)+(4+3)+3=28\)
而选择(4,6),则有\(sum=(6+5+4+3)+(5+3)=26\)
综上我们得出两个结论,第一需要多轮操作才可能最小化\(sum\),第二最大独立集的贪心思路是错误的。
此时一个关键问题摆在了我们面前,我们最多可能需要多少轮操作呢?
构造多轮的树结构不是那么容易,赛时也挺难悟到,这里给出一个建树方式
-
初始只有两个节点1,此时我们需要一轮操作
-
添加一个节点2,此时我们需要两轮操作
-
在1和2节点上添加4和5,此时由于我们需要先移除(4,5),所以需要三轮操作
-
下面是递推的构造,在之前出现的所有节点上,添加(8-11),此时第一轮移除(8-11)是最优的,加上之前的三轮,我们就需要四轮操作
-
...重复下去,每轮添加的节点个数是\(2^{x-2}\),节点权值分别是\([2^{x-1},2^{x-1}+2^{x-2}-1]\)。
这样需要x轮操作移除所有节点的情况下,节点总数需要\(2^{x-1}\),而节点总数是\(n\)的情况下,我们至多需要的轮数就是\(logn+1\)
这是第一个难点,需要能推算到至多需要的轮数是\(logn+1\) 不妨设\(T=logn+1\)
讨论真正的做法前,我们需要反思以下:
之前常做的树形dp往往是对一个节点的操作是选或者不选这种两个状态的转移,典型就是树的最大独立集。
就算是多节点多状态也往往是题意里推导出的带有意义的固定个数,比如968. 监控二叉树
这导致很容易把树形dp中dp的内涵忽略,对本题而言,虽然是树形dp,但是节点却是有\(T=logn+1\)这样一个状态数。
因此第二个难点就是,要能发现树形dp的状态需要定义为数组\(dp[i][j]\),其中\(i\)为节点,\(j∈[1,T]\)。每个节点可能出现在任何一轮,而如果节点在第x轮移除,那么他产生的对\(sum\)的贡献为\(x*a_i\)。
然后如何进行状态转移呢?每个节点需要基于所有子节点的状态进行转移
这是难点三,推导转移方程。每个节点的初始都是一致的,即每轮的自身代价。
这里假设节点i有k个子节点,转移中,是对于每个自身轮数,需要依次累加子节点不同轮数的最小\(sum\)
即\(f[i][j]= dp[i][j] + \sum_{s=1}^k(min(f[s][t]),(t∈[1,T],t≠j))\)
简单枚举每个\(j∈[1,T]\),再枚举子节点\(t∈[1,T]\),取\(j≠t\)的最小值,复杂度为\(O(log^2n)\)
整体复杂度\(O(nlog^2n)\)
不过这里有个经典小技巧用于处理这个问题。
对于每个\(t∈[1,T]\),可以遍历依次统计\(dp[s][t]\)的最小值min和次小值min2
然后对于每个\(j\),则有
所以整体复杂度可以降为\(O(nlogn)\)
核心代码如下(未使用该技巧,本题没卡log)
long[][] f = new long[n][];
for(int i = 0; i < n; i++) f[i] = new long[T];
void DFS(int u, int fa)
{for(int k = 0; k < T; k++){f[u][k] = (k + 1) * w[u];}foreach(int v in g[u]){if(v == fa) continue;DFS(v, u);for(int k = 0; k < T; k++){long min = long.MaxValue;for(int s = 0; s < T; s++){if(s != k) min = Math.Min(min, f[v][s]);}f[u][k] += min;}}
}
DFS(0, -1);
long ans = long.MaxValue;
for(int i = 0; i < T; i++) ans = Math.Min(f[0][i], ans);
Print(ans);