19.10.01 acm E:Lowest Common Ancestor

题目描述

一棵有根树,对于每个点 i i ,求 j = 1 i 1 w l c a ( i , j ) \sum_{j=1}^{i-1}w_{lca(i,j)}

数据范围

n 2 × 1 0 5 , 1 w i 1 0 4 n \le 2 \times 10^5,1 \le w_i \le 10^4

题解

我们可以考虑枚举 l c a lca 去更新答案

对于每个点 x x ,如果它成为两个点的 l c a lca ,那这两个点肯定来自不同的子树

那我们可以建立一个线段树表示这个区间的答案总和(?),然后进行线段树合并即可

具体地话就是考虑合并到 x , y x,y 两个节点上,那 x x 的左儿子会对 y y 的右儿子造成贡献,同理 y y 的左儿子会对 x x 的右儿子造成贡献,所以我们再多维护一个区间有多少个数出现即可

代码

#include <bits/stdc++.h>
using namespace std;
const int N=2e5+5,M=N*50;
int a[N],TT,n,T[N],t,nx[N],hd[N],V[N];
int s[M],f[M],ls[M],rs[M],tg[M];
void add(int u,int v){
	nx[++t]=hd[u];V[hd[u]=t]=v;
}
#define mid ((l+r)>>1)
void insert(int &x,int l,int r,int v){
	s[x=++t]++;
	if (l==r) return;
	if (mid>=v) insert(ls[x],l,mid,v);
	else insert(rs[x],mid+1,r,v);
}
void down(int x){
	if (ls[x]) f[ls[x]]+=tg[x],tg[ls[x]]+=tg[x];
	if (rs[x]) f[rs[x]]+=tg[x],tg[rs[x]]+=tg[x];
	tg[x]=0;
}
int merge(int x,int y,int c,int d,int v){
	if (!x && !y) return 0;
	if (!x){f[y]+=d*v;tg[y]+=d*v;return y;}
	if (!y){f[x]+=c*v;tg[x]+=c*v;return x;}
	down(x);down(y);int z=++t;
	ls[z]=merge(ls[x],ls[y],c,d,v);
	rs[z]=merge(rs[x],rs[y],c+s[ls[y]],d+s[ls[x]],v);
	f[z]=f[ls[z]]+f[rs[z]];
	s[z]=s[ls[z]]+s[rs[z]];
	return z;
}
void dfs(int x,int fr){
	insert(T[x],1,n,x);
	for (int i=hd[x];i;i=nx[i])
		if (V[i]!=fr){
			dfs(V[i],x),
			T[x]=merge(T[x],T[V[i]],0,0,a[x]);
		}
}
void put(int x,int l,int r){
	if (l==r){
		if (l>1) printf("%d\n",f[x]);return;
	}
	down(x);put(ls[x],l,mid);put(rs[x],mid+1,r);
}
void work(){
	t=0;
	for (int i=1;i<=n;i++)
		scanf("%d",&a[i]),hd[i]=0;
	for (int i=2,x;i<=n;i++)
		scanf("%d",&x),add(x,i);t=0;
	dfs(1,0);put(T[1],1,n);
	for (int i=1;i<=t;i++)
		ls[i]=rs[i]=tg[i]=f[i]=s[i]=0;
}
int main(){
	while(~scanf("%d",&n)) work();
	return 0;
}
发布了107 篇原创文章 · 获赞 5 · 访问量 8015

猜你喜欢

转载自blog.csdn.net/Johnny817/article/details/102102751