CF1059E Split the Tree

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;
} 

posted @ 2021-03-30 16:39  Troverld  阅读(35)  评论(0编辑  收藏  举报