- 我们可以通过
dfn
和后序dfn
刻画一条链以及其两边的限制。
我们可以免费选择一条链上所有点的一个苹果。
考虑这个链将这个树分成三块,链,左,右。链左边可以通过 dfs
背包来实现,右边可以用后序 dfn
进行 dp
(具体见P6326
),然后枚举叶子,合并三个块 \(O(nk^2)\)。
考虑将链上的背包干掉,如果我们用 dfs 扫 \(u\) 链前左边的背包的时候不要求必须选择 \(u\) 到根节点链上的苹果,同时将 \(i\) 到根节点上的苹果数量变成 \(a_i-1\) 个,回溯的时候再加上最后那一个东西即可。
代码很易懂,可以看看。
#include<bits/stdc++.h>
using namespace std;
#define N 20005
#define V 500005
#define inf 0x3f3f3f3f
int t,n,m,tot,val[N],ans;
vector<int> G[N],f[N],g[N];
int w[N],c[N],dad[N],dfn[N],rnk[N],siz[N],leaf[N],vis[N],q[V];
void dfs(int u){
siz[u]=leaf[u]=1;val[u]+=w[u];
for(int v:G[u])if(v^dad[u])val[v]=val[u],dfs(v),siz[u]+=siz[v],leaf[u]=0;
rnk[dfn[u]=++tot]=u;
}
void dfs2(int u){
vis[u]=1;
if(leaf[u]){
for(int k=0;k<=m;k++)ans=max(ans,g[u][k]+f[dfn[u]-1][m-k]+val[u]);
}
reverse(G[u].begin(),G[u].end());
for(int v:G[u])if(v^dad[u]){
int l=0,r=-1;
for(int j=0;j<=m;j++){
while(l<=r&&j-q[l]>c[v]-1)++l;
while(l<=r&&g[u][q[r]]+(j-q[r])*w[v]<g[u][j])--r;q[++r]=j;
if(l<=r)g[v][j]=max(g[v][j],g[u][q[l]]+(j-q[l])*w[v]);
}
dfs2(v);
for(int j=1;j<=m;j++)g[u][j]=max(g[v][j-1]+w[v],g[u][j]);
}
}
void work(){
for(int i=0;i<=n;i++)G[i].clear(),vector<int>().swap(f[i]),vector<int>().swap(g[i]),val[i]=0;ans=-inf;tot=0;
scanf("%d%d",&n,&m);
for(int i=0;i<=n;i++)f[i].resize(m+1,-inf),g[i].resize(m+1,-inf);
for(int u=1;u<=n;u++)scanf("%d%d%d",&dad[u],&c[u],&w[u]),G[dad[u]].push_back(u);
dfs(1);
f[0][0]=0;
for(int i=1;i<=n;i++){
int u=rnk[i];int l=0,r=-1;
for(int j=0;j<=m;j++)f[i][j]=max(f[i][j],f[i-siz[u]][j]);
for(int j=0;j<=m;j++){
while(l<=r&&j-q[l]>c[u])++l;
if(l<=r)f[i][j]=max(f[i-1][q[l]]+(j-q[l])*w[u],f[i][j]);
while(l<=r&&f[i-1][q[r]]+(j-q[r])*w[u]<f[i-1][j])--r;
q[++r]=j;
}
}
for(int i=0;i<=n;i++)for(int j=1;j<=m;j++)f[i][j]=max(f[i][j],f[i][j-1]);
for(int i=0;i<c[1];i++)g[1][i]=w[1]*i;
dfs2(1);
printf("%d\n",ans);
}
int main(){
scanf("%d",&t);
while(t--)work();
}