题目分析
题目可以看做,每拿一个苹果要花一元钱,只有 k k k元钱,每一个深度上都有一个苹果可以免费获得,问可以获得的最大幸福度。
由于每个深度都能有一个苹果免费获得,所以肯定存在一个叶子节点,它上面的苹果被取了至少一个。因此,也可以看做将一条从根到叶子的链上的苹果都免费取一个,剩下的再做背包。取链的方案只有 O ( n ) O(n) O(n)种。
可是链上的节点,每个节点都只能免费取一个苹果,剩下的那些怎么办?将这条链上的 a i a_i ai都减1然后重新DP一次?
于是将所有节点拆点,拆为存在 1 1 1个苹果的点和存在 a i − 1 a_i-1 ai−1个苹果的点,后者是前者的儿子,也就是,这个点要取了第一个苹果后,才能取第二个苹果。
接下来使用dfs序出序的手法,也就是dfs这棵树,当从一个节点回溯向它的父亲时,给这个节点编号。
若枚举的这条链的叶子(链上出序最小节点)的出序是 p o s i pos_i posi,那么很显然出序在 [ 1 , p o s i ] [1,pos_i] [1,posi]都是不在链上的节点。若将每一个节点往下dfs儿子的顺序反过来再做一遍dfs,那么出序在 [ 1 , p o s i ′ ] [1,pos'_i] [1,posi′]区间内的点和原来出序在 [ 1 , p o s i ] [1,pos_i] [1,posi]的点,正好就是(叶子被拆出来的第二个点)+(不在链上的点)
于是我们可以对于两种dfs出序序列,前缀都做DP, f ( i , j ) f(i,j) f(i,j)表示前 i i i个节点取 j j j个苹果的最大幸福度。设 s z p i sz_{p_i} szpi表示点 p i p_i pi的子树大小,根据若这个点不取苹果,则它的子树不能取苹果,而子树的dfs出序跟它的又是连着的,可以得到DP方程: f ( i , j ) = m a x ( f ( i − 1 , j − t ) + t v p i , f ( i − s z p i , j ) ) f(i,j)=max(f(i-1,j-t)+tv_{p_i},f(i-sz_{p_i},j)) f(i,j)=max(f(i−1,j−t)+tvpi,f(i−szpi,j))
类似多重背包单调队列优化的手法,这个也可以单调队列优化。
然后枚举链,枚举用两种出序划分开的两个集合各取多少个苹果,合并信息,得到答案。
代码
#include<bits/stdc++.h>
using namespace std;
#define RI register int
int read() {
int q=0;char ch=' ';
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();
return q;
}
const int N=40005;
int kas,n,SZ,K,tim1,tim2,ans;
vector<int> tr[N];
int not_leaf[N],a[N],v[N],fa[N],Lsum[N];
int pos1[N],pos2[N],repos1[N],repos2[N],sz[N];
int f1[60000005],f2[60000005],q[500005];
void dfs1(int x) {
sz[x]=1;
for(RI i=0;i<tr[x].size();++i)
Lsum[tr[x][i]]=Lsum[x]+v[tr[x][i]],dfs1(tr[x][i]),sz[x]+=sz[tr[x][i]];
pos1[x]=++tim1,repos1[tim1]=x;
}
void dfs2(int x) {
for(RI i=0;i<tr[x].size();++i) dfs2(tr[x][i]);
pos2[x]=++tim2,repos2[tim2]=x;
}
void DP(int *p,int *f) {
for(RI j=1;j<=K;++j) f[j+1]=-0x3f3f3f3f;
for(RI i=1;i<=SZ;++i) {
int x=p[i],he=1,ta=0;
for(RI j=0;j<=K;++j) {
while(he<=ta&&j-q[he]>a[x]) ++he;
int now=i*(K+1)+j+1;
f[now]=f[(i-sz[x])*(K+1)+j+1];
if(he<=ta) f[now]=max(f[now],f[(i-1)*(K+1)+q[he]+1]+(j-q[he])*v[x]);
while(he<=ta&&f[(i-1)*(K+1)+q[ta]+1]-q[ta]*v[x]<=
f[(i-1)*(K+1)+j+1]-j*v[x]) --ta;
q[++ta]=j;
}
}
}
void work() {
Lsum[1]=v[1],dfs1(1);
for(RI i=1;i<=n;++i) reverse(tr[i].begin(),tr[i].end());
dfs2(1),DP(repos1,f1),DP(repos2,f2);
for(RI i=1;i<=n;++i) {
if(not_leaf[i]) continue;
for(RI j=0;j<=K;++j) {
ans=max(ans,f1[(pos1[i]-1)*(K+1)+j+1]+
f2[(pos2[i]-sz[i])*(K+1)+K-j+1]+Lsum[i]);//子树出现多次
}
}
printf("%d\n",ans);
}
void clear_all() {
tim1=tim2=ans=0;
for(RI i=1;i<=SZ;++i) tr[i].clear(),not_leaf[i]=0;
for(RI i=0;i<=(SZ+1)*(K+1);++i) f1[i]=f2[i]=0;
}
int main()
{
kas=read();
while(kas--) {
SZ=n=read(),K=read();
for(RI i=1;i<=n;++i)
fa[i]=read(),not_leaf[fa[i]]=1,a[i]=read(),v[i]=read();
for(RI i=1;i<=n;++i) {
if(fa[i]) tr[fa[i]].push_back(i);
if(a[i]>1) ++SZ,a[SZ]=a[i]-1,v[SZ]=v[i],a[i]=1,tr[i].push_back(SZ);
}
work(),clear_all();
}
return 0;
}