D. The Omnipotent Monster Killer

D. The Omnipotent Monster Killer

题目大意:
有一棵树,树节点数不超过3·105,每个节点的权值,定义为数组a(ai<1012),初始sum=0,每一轮执行如下操作:

  • 计算当前剩余所有的点权和,累计到sum
  • 任选若干个互不相邻的节点并移除

重复这个操作直到树上没有任何节点为止,最小化sum

整体感觉这道题带来的启发很多,值得慢慢细品。

首先第一个误区以为可以贪心的两轮操作结束,比如:

先移除2 4,再移除1 3。
但是很容易举出反例:

显然要先移除4 5,再移除一个2,最后移除剩余的1,需要三轮才能完成。
三轮能否确定结束呢?如果是链式结构确实是可以的,因为链式结构中,任意节点最多只有两个节点相邻,这三者互不相同也只有三轮操作。
而在树上,某个节点N相邻可能有n个Xi,如果每一个均在不同轮次移除,那么节点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)是最优的,加上之前的三轮,我们就需要四轮操作

  • ...重复下去,每轮添加的节点个数是2x2,节点权值分别是[2x1,2x1+2x21]

这样需要x轮操作移除所有节点的情况下,节点总数需要2x1,而节点总数是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的贡献为xai

然后如何进行状态转移呢?每个节点需要基于所有子节点的状态进行转移
这是难点三,推导转移方程。每个节点的初始都是一致的,即每轮的自身代价。
这里假设节点i有k个子节点,转移中,是对于每个自身轮数,需要依次累加子节点不同轮数的最小sum
f[i][j]=dp[i][j]+s=1k(min(f[s][t]),(t[1,T],tj))
简单枚举每个j[1,T],再枚举子节点t[1,T],取jt的最小值,复杂度为O(log2n)
整体复杂度O(nlog2n)
不过这里有个经典小技巧用于处理这个问题。
对于每个t[1,T],可以遍历依次统计dp[s][t]的最小值min和次小值min2
然后对于每个j,则有

(1) f[i][j]={min,f[s][t]minmin2,f[s][t]=min

所以整体复杂度可以降为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);
posted @   云上寒烟  阅读(33)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示