【JLOI2014】松鼠的新家(lca+树上前缀和)

传送门

对于这道题,我们不难想到,对于两个点,我们可以求出他们的lca,然后顺着链暴力往上修改。

但是这样时间复杂度是很不乐观的,最多可以达到n^2。

这里介绍一种可以用来修改树上路径的方法——子树前缀和。

设定一个修改数组change。如果要对x到y路径上的所有点权值+k,lca为z。那么change[x]+=k,change[y]+=k,change[z]-=k,change[fa[z]]-=k。这样如果最后对change[i]求子树前缀和的话,最后得到的结果就是i权值的修改量。

特点:可以O(1)修改,但是只能一次性查询(因为对于一系列的询问,需要O(n)前缀和预处理)

#include<bits/stdc++.h>
#define N 300005
using namespace std;
int n,sx[N],first[N],tot,down[N],up[N][21],change[N];
struct node
{
	int next,to;
}edge[2*N];
inline void addedge(int x,int y)
{
	++tot;
	edge[tot].to=y;
	edge[tot].next=first[x];
	first[x]=tot;
}
inline void dfs1(int now,int fa)
{
	up[now][0]=fa;
	down[now]=down[fa]+1;
	for(int i=1;i<=20;i++)	up[now][i]=up[up[now][i-1]][i-1];
	for(int u=first[now];u;u=edge[u].next)
	{
		int vis=edge[u].to;
		if(vis==fa)	continue;
		dfs1(vis,now);
	}
}
inline int lca(int x,int y)
{
	if(down[x]<down[y])	swap(x,y);	//默认x比y深;
	for(int i=20;i>=0;i--)
	{
		if(down[up[x][i]]>=down[y])	x=up[x][i];
	} 
	if(x==y)	return x;
	for(int i=20;i>=0;i--)
	{
		if(up[x][i]!=up[y][i])
		{
			x=up[x][i];
			y=up[y][i];
		}
	}
	return up[x][0];
}
inline void dfs2(int now)
{
	for(int u=first[now];u;u=edge[u].next)
	{
		int vis=edge[u].to;
		if(vis==up[now][0])	continue;
		dfs2(vis);
		change[now]+=change[vis];
	}
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(NULL),cout.tie(NULL);
	
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>sx[i];	
	}
	for(int i=1;i<=n-1;i++)
	{
		int x,y;
		cin>>x>>y;
		addedge(x,y);
		addedge(y,x);
	}
	dfs1(1,0);
	/*This is used to debug
	for(int i=1;i<=n;i++)
	{
		for(int j=0;j<=20;j++)
		{
			cout<<up[i][j]<<" ";
		}
		cout<<endl;
	} */
	 
	for(int i=1;i<n;i++)
	{
		int x=sx[i];
		int y=sx[i+1];
		change[x]++;
		change[y]++;
		change[lca(x,y)]--;
		change[up[lca(x,y)][0]]--;
	}
	dfs2(1);
	for(int i=2;i<=n;i++)	change[sx[i]]--;//既是终点,又是起点的点
	for(int i=1;i<=n;i++)
	{
		cout<<change[i]<<'\n';
	} 
	return 0;
}
posted @ 2018-08-11 10:26  Patrickpwq  阅读(139)  评论(0)    收藏  举报