关于子树合并背包类型的dp的复杂度

本文探讨了WC2019比赛中的一道数树问题,该问题涉及子树合并的复杂度分析。原始的直接DP方法具有O(n^2)的时间复杂度,但通过证明合并大小为a、b的子树复杂度为O(ab),可以优化到O(n^2)。此外,当仅考虑小于k的部分时,复杂度进一步降低到O(nk)。文章还提到了使用生成函数的方法进行O(n)优化,并提供了多项式EXP模板的应用建议。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前不久,WC2019考了一道风格新奇的数树。
通过层层容斥化式猜结论,(子任务2)终于化简成了一道小清新树形DP:

一个边集T的权值等于每个连通块的权值之积,每个连通块的权值等于 k × k× k×联通块大小,求所有边集的权值之和。一个简单的实现方式是直接 d p dp dp,令 d p ( i , j ) dp(i,j) dp(i,j)表示划分到了 i i i这个子树, i i i所在的联通大小为 j j j时的所有边集权值之和,直接实现的复杂度是 O ( n 2 ) O(n^2) O(n2)的并不能通过 n = 1 0 5 n=10^5 n=105的诡异数据。

且不看最后一句话,这段话差点让我。。。。。。怎么 O ( n 2 ) O(n^2) O(n2)DP啊,一个n枚举点,一个n枚举当前大小,一个n枚举增加的大小,这活脱脱的 O ( n 3 ) O(n^3) O(n3)?然后我就naive了。原来大佬早有证明。具体是这样的,合并大小为a,b的子树复杂度是 O ( a b ) O(ab) O(ab),可以看成a子树内任选一点,b子树内任选一点进行匹配,不管怎么合并任意两个点只会在其lca匹配一次,所以是 O ( n 2 ) O(n^2) O(n2)的。

所以我的树形DP都白学了。。。。。。

其次,如果第二位大于k的部分不需要,那么复杂度是 O ( n k ) O(nk) O(nk)
这就导致2018九省联考秘密袭击这道题的 O ( n 2 k ) O(n^2k) O(n2k)大力树上DP远远快于我的 O ( n 2 log ⁡ 2 n ) O(n^2\log^2 n) O(n2log2n)的FFT和 O ( n 2 log ⁡ n ) O(n^2\log n) O(n2logn)的线段树合并维护整体DP+拉格朗日插值 O ( n 2 ) O(n^2) O(n2)维护卷积555

(子任务2的)正解?

我们可以考虑每个贡献的组合意义,大小为 a i a_i ai 的连通分量贡献相当于在这个联通分量中选取一个点产生 K K K 的乘积贡献。
据此我们可以优化状态表示: f [ u ] [ 0 / 1 ] \mathrm{f}[u][0/1] f[u][0/1] 表示 u u u 的子树中,当前连通分量是否已经做出贡献,这里为了方便转移,当前连通分量如果已经做出贡献就计入答案。

   LL K, f[MN], g[MN];
    void DFS(int u, int fz) {
        f[u] = 1, g[u] = K;
        for (int i = h[u]; i; i = nxt[i]) if (to[i] != fz) {
            DFS(to[i], u);
            g[u] = (f[u] * g[to[i]] + g[u] * f[to[i]] + g[u] * g[to[i]]) % Mod;
            f[u] = (f[u] * f[to[i]] + f[u] * g[to[i]]) % Mod;
        }
    }

巧妙利用组合意义?O(n)。

当然老年选手可以这样:

那么我们考虑每一个dp数组对应的生成函数,我们把点u的生成函数记做

f u ( z ) = ∑ i d p ( u , i ) z i f_{u}(z)=\sum_{i}dp(u,i)z^{i} fu(z)=idp(u,i)zi

那么我们合并u,v时的转移可以写成

f u ( z ) = f u ( z ) ( k f v ′ ( 1 ) + f v ( z ) ) f_{u}(z)=f_{u}(z)(kf_{v}'(1)+f_{v}(z)) fu(z)=fu(z)(kfv(1)+fv(z))
而我们最后求的答案其实就是 k f 1 ′ ( 1 ) kf_{1}'(1) kf1(1)
那么不妨设 g u = k f u ′ ( 1 ) g_{u}=kf_{u}'(1) gu=kfu(1)
那么我们考虑合并了一个子树的时候 g u g_{u} gu 作何改变

f u ′ = ( f u g v + f u f v ) ′ f_{u}'=(f_{u}g_{v}+f_{u}f_{v})' fu=(fugv+fufv)

f u ′ = g v f u ′ + f u ′ f v + f u f v ′ f'_{u}=g_{v}f_{u}'+f_{u}'f_{v}+f_{u}f_{v}' fu=gvfu+fufv+fufv
k f u ′ ( 1 ) = g v k f u ′ ( 1 ) + k f u ′ ( 1 ) f v ( 1 ) + f u k f v ′ kf_{u}'(1)=g_{v}kf_{u}'(1)+kf_{u}'(1)f_{v}(1)+f_{u}kf_{v}' kfu(1)=gvkfu(1)+kfu(1)fv(1)+fukfv
g u = g v g u + g u f v ( 1 ) + f u ( 1 ) g v g_{u}=g_{v}g_{u}+g_{u}f_{v}(1)+f_{u}(1)g_{v} gu=gvgu+gufv(1)+fu(1)gv
看起来这个式子十分的简单,如果我们设 t u = f u ( 1 ) t_{u}=f_{u}(1) tu=fu(1)的话我们可以得到这样的两个式子

t u = t u ( g v + t v ) t_{u}=t_{u}(g_{v}+t_{v}) tu=tu(gv+tv)
g u = g u g v + g u t v + t u g v g_{u}=g_{u}g_{v}+g_{u}t_{v}+t_{u}g_{v} gu=gugv+gutv+tugv

如果您的生命已如风中残烛:
直接Ctrl+C Ctrl+V 多项式EXP模板即可。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值