LXIV.CF1059E Split the Tree
我们假设对于每个位置,已经求出了它可以往上延伸的长度\(len[x]\),然后考虑DP。
设\(g[x]\)表示子树被分完后的最小边的数量。再设\(f[x]\)表示当这个数量最小时,点\(x\)能够往上延伸的最长长度。
这运用了贪心的思想:因为\(g[x]\)少一条边,肯定是要比\(f[x]\)无论大多少都是要更优的。\(f[x]\)再大,也只对一条边有效,\(f\)中一条边和\(g\)中一条边,不都是一样的吗?
我们可以很轻松地得到转移方程:
\(f[x]=\max\limits_{y\in Sons_x}\{f[y]\}-1,g[x]=\sum\limits_{y\in Sons_x}g[y]\)
如果在上面的转移方程中,得到了\(f[x]=-1\),那就意味着必须在\(x\)位置开新边,令\(f[x]=len[x]\),\(g[x]\)加一。
现在主要的部分就是求出\(len[x]\)了。这个可以通过倍增法在\(O(n\log n)\)时间里预处理出来。
则总复杂度为\(O(n\log n)\)。
代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,L,S,val[100100],len[100100],anc[100100][20],sum[100100],dep[100100],f[100100],g[100100];
vector<int>v[100100];
void dfs1(int x){
for(int i=1;(1<<i)<=dep[x];i++)anc[x][i]=anc[anc[x][i-1]][i-1];
for(int i=19,y=x;i>=0;i--){
if(!anc[y][i])continue;
if(sum[x]-sum[anc[y][i]]+val[anc[y][i]]>S)continue;
if(dep[x]-dep[anc[y][i]]>=L)continue;
len[x]+=(1<<i),y=anc[y][i];
}
for(auto y:v[x])anc[y][0]=x,dep[y]=dep[x]+1,sum[y]=sum[x]+val[y],dfs1(y);
}
void dfs2(int x){
for(auto y:v[x])dfs2(y),f[x]=max(f[x],f[y]),g[x]+=g[y];
f[x]--;
if(f[x]==-1)f[x]=len[x],g[x]++;
}
signed main(){
scanf("%lld%lld%lld",&n,&L,&S);
for(int i=1;i<=n;i++){
scanf("%lld",&val[i]);
if(val[i]>S){puts("-1");return 0;}
}
for(int i=2,x;i<=n;i++)scanf("%lld",&x),v[x].push_back(i);
dep[1]=1,sum[1]=val[1],dfs1(1),dfs2(1);
// for(int i=1;i<=n;i++)printf("%lld ",len[i]);puts("");
printf("%lld\n",g[1]);
return 0;
}