原问题等价于对于每一个点
i,求树上包含
i 的所有树上独立集的最大点权之和。
测试点 1∼2
暴力枚举派遣的队员的集合,如果其满足限制,则统计其贡献。
时间复杂度
O(2nn)。
测试点 3
尝试计算对于
k=1,2…n,包含
1 号点,且
v 的最大值
恰好为
k 的独立集数量,考虑使用树形 DP:
设计 DP 状态
fi,j,0/1 表示在
i 的子树内,选择一个
v 的最大值为
j 的集合,且不包含/包含
i 的集合数量。
求解
i 点的状态时依次将每一个儿子
v 合并。
具体的贡献形式有
fi,j,0×(fv,k,0+fv,k,1)→fi,max(j,k),0,
fi,j,1×fv,k,0→fi,max(j,k),1。
单次合并时枚举
j,k,时间复杂度
O(n2)。
最终
1 号点的答案就是
i=1∑nif1,i,1,时间复杂度
O(n3)。
对于每一个点进行这一过程,时间复杂度
O(n4)。
测试点 4∼5
上面的合并结构时
max 卷积,考虑较为一般的形式:
hk=max(i,j)=k∑figj。
有
k=1∑mhk=k=1∑mmax(i,j)=k∑figj=max(i,j)≤m∑figj=(i≤m∑fi)(j≤m∑gj)。
因此,我们对
f 和
g 求前缀和,对位相乘,然后再做差分,就可以得到
h。
这样单次合并的复杂度可以优化至
O(n),总时间复杂度优化到
O(n3)。
测试点 4∼5 另解
考虑放宽限制,计算对于
k=1,2…n,包含
1 号点,且
v 的最大值
至多为
k 的独立集数量。
发现这时每一个点只有可以选择和不可选择两种状态,对于单个的
k,可以直接树形 DP
O(n) 解决。
求解一个点的答案时间复杂度为
O(n2),总时间复杂度为
O(n3)。
测试点 6∼7
考虑
k 从小到大的过程,每一个点有且仅有一次从不可选择到可选择的变化,因此考虑使用数据结构维护 DP 值变化的过程,这就是经典的动态 DP 问题。
使用树剖线段树总时间复杂度为
O(n2log2n),使用全局平衡二叉树时间复杂度为
O(n2logn),但实现效率差距不大,可能需要卡常。
测试点 8∼9
对于每一个点进行一次求解太麻烦了,发现
4∼5 的两种做法都是可以使用换根 DP 进行求解,时间复杂度
O(n2)。
测试点 10∼11
注意到在上面的做法中,DP 状态第第二维实际上只与
vi 的值域有关,更本质得说,只与本质不同的
vi 的数量有关。
所以复杂度实际上是
O(Vn) 的,其中
V 是本质不同的
vi 的数量。
满分做法
使用转置原理求解这个问题。
考虑这样一个问题:记
ai,j 为包含点
i 且
v 最大的点为
j 的独立集数量,那么记
n 阶方阵
A=(ai,j)。记向量
a=[v1,v2…vn]⊤,则我们要求的就是向量
b=Aa。
考虑这个问题的转置,给每一个点设置一个额外的权值
wi,记
a′=[w1,w2…wn]⊤,考虑问题
b′=Aa′。
这个问题就是:对于每一个
i,求所有
v 权值最大值等于
i 的独立集的
w 权值之和的和。
发现新问题是可以使用树剖线段树解决的。
考虑按照点的
v 权值从小到大一次加入每一个点。
实时维护
fi,0/1 表示在
i 的子树内不包含/包含
i 的情况下,可能的独立集数量和
w 权值之和的和。
事实上
f 是一个二元组
(cnt,val)。
对其进行的加法和乘法运算分别为
(cnt1,val1)+(cnt2,val2)=(cnt1+cnt2,val1+val2),
(cnt1,val1)×(cnt2,val2)=(cnt1×cnt2,cnt1×val2+cnt2×val1)。
首先考虑转移:
fi,0=v∈soni∏(fv,1+fv,0),fi,1=opiv∈soni∏fv,0。
其中
opi 在
i 还没有出现过是为
(0,0),在
i 出现过之后为
(1,wi),单独将重儿子
u 提取出来,此时记
soni 为除
u 之外的所有儿子,那么就可以得到矩阵转移:
[fi,0fi,1]=v∈soni∏(fv,0+fv,1)opv∈soni∏fv,0v∈soni∏(fv,0+fv,1)0[fu,0fu,1]
不妨记这个矩阵为
Bi。每一次修改相当于修改一个点
u 的
op,考虑修改之后,矩阵
B 会发生变化的位置只有
u 到根的路径上跳轻边跳到的点,根据重链剖分的性质,这样个点只有
O(logn) 个。
对于每一个节点建立维护
B 矩阵中的两个连乘
v∈soni∏(fv,0+fv,1) 和
v∈soni∏fv,0 的线段树,这样就能够支持对于单个轻儿子的
f 的修改。
同时对于每一条重链维护矩阵
B 的线段树,这样边能够支持对于单个
B 的修改以及链顶节点的
f 值得查询。
具体的修改过程,就是根据计算出的
v∈soni∏(fv,0+fv,1) 和
v∈soni∏fv,0 的值更新新的
Bu。就可以通过整条链上面的矩阵来计算出新的
ftopu,0 和
ftopu,1,然后就可以更新
fatopu 的线段树,以此类推一直更新到
f1,0 和
f1,1。
因此按照
v 从小到大修改每一个
u,每一次修改后
f1,0+f1,1 的增加量就是所有包含
u 且以
u 为最大值的独立集数量以及重量之和。
假设每
i 次修改之后
f1,0+f1,1 的值为
lasi。
那么对于第
i 小的来说答案就是
lasi−lasi−1 的第二维。
发现所有对于第二维的操作都是形如
ai←ai+aj×k 的操作,所以对其进行转置操作有
aj←aj+ai×k。
对于第二维的加法,都可以写成
val1←val1+val2 的形式,因此转置之后有
val2←val1+val2,减法同理。
对于第二维的乘法,可以写成
val3←val3+val1×cnt2+val2×cnt1 的形式,因此转置之后有
val1←val1+val3×cnt2,
val2←val2+val3×cnt1。
因此可以直接封装出所有操作的逆,然后倒序执行之前执行过的所有操作即可。
时间复杂度
O(nlog2n),空间复杂度
O(nlogn) 或
O(n)。
可以将树剖线段树替换成全局平衡二叉树,将时间复杂度优化至
O(nlogn),但实际效率差距不大。