BZOJ3626 [LNOI2014]LCA(树链剖分)

【题解】


首先考虑任意两点u,v的LCA的deep:
若将0到u路径上所有点标记,则deep[LCA(u,v)]等于从v上溯到的第一个被标记点的deep,而再往上的话一直到根,经过的点都是被标记点 
由前缀和的思想,将0到u路径上所有点权值设为1,其他点权值为0,那么deep[LCA(u,v)]等于SUM(0到v路径上的点权和)
再进一步想,deep[LCA(u1,v)]+deep[LCA(u2,v)]怎么求?
1.可以将0到v路径上的点权设为1,再分别求u1,u2到根的点权和,但这样无法优化时间,相当于暴力 
2.也可以将0到u1路径上的点权加1,再将0到u2路径上的点权加1,然后求v到根的点权和,因为每个值为1的权相当于深度的一步,是可叠加的 
按照方法2,在 将0至ui的路径上的结点权值加1 之后(ui取遍[l,r]),就可以一并求出sigma_{l<=i<=r}dep[LCA(i,z)]了 
对于q次询问,不能每次都把点权变来变去,这里再次利用叠加性与前缀和思想,将ans[l,r]拆开,转换为ans[0,r]-ans[0,l-1],求解两个子问题即可 
这样,我们可以预处理完1~n-1每个点的影响:每个点到根所经过的点的权值加1,链剖+线段树维护路径点权和 
在这个过程中,2*q个被拆开的询问离线处理即可,比如询问ans[0,x]:在处理完点x后,求它到根的路径上的权值和,并记录下这来自第几个问题就行了 

本题中的重要思想:区间查询都可以转化为前缀和的差值,还有如何将LCA问题转化为维护树上路径信息的问题


【代码】

#include<stdio.h>
#include<stdlib.h>
#include<vector>
#define MOD 201314
using namespace std;
typedef long long LL;
vector<int> G[50005],A[50005],N[50005];
int fa[50005]={0},size[50005]={0},son[50005]={0},top[50005]={0},pos[50005]={0},p[50005]={0};
LL sumv[200000]={0},addv[200000]={0},Q[50005][5]={0};
int n,e=0,tot=0;
LL jdz(LL x)
{
	if(x<0) x=-x;
	return x;
}
void dfs1(int x)
{
	int i;
	size[x]=1;
	for(i=0;i<G[x].size();i++)
	{
		dfs1(G[x][i]);
		size[x]+=size[G[x][i]];
		if(son[x]==0||size[son[x]]<size[G[x][i]]) son[x]=G[x][i];
	}
}
void dfs2(int x,int t)
{
	int i;
	top[x]=t;
	pos[x]=++tot;
	if(son[x]!=0) dfs2(son[x],t);
	for(i=0;i<G[x].size();i++)
		if(G[x][i]!=son[x]) dfs2(G[x][i],G[x][i]);
}
void xg(LL p,int x,int y,int o,int left,int right)
{
	int mid=(left+right)/2;
	if(x<=left&&right<=y) addv[o]+=p;
	else
	{
		if(x<=mid) xg(p,x,y,o*2,left,mid);
		if(y>mid) xg(p,x,y,o*2+1,mid+1,right);
	}
	if(left<right) sumv[o]=sumv[o*2]+sumv[o*2+1]+addv[o]*(LL)(right-left+1);
	else sumv[o]+=p;
}
LL cx(int x,int y,int o,int left,int right,LL add)
{
	LL ans=0;
	int mid=(left+right)/2;
	if(x<=left&&right<=y) return sumv[o]+add*(LL)(right-left+1);
	add+=addv[o];
	if(x<=mid) ans+=cx(x,y,o*2,left,mid,add);
	if(y>mid) ans+=cx(x,y,o*2+1,mid+1,right,add);
	return ans;
}
void update(int x)
{
	int tx=top[x];
	while(tx!=0)
	{
		xg(1,pos[tx],pos[x],1,1,n);
		x=fa[tx];
		tx=top[x];
	}
	xg(1,pos[0],pos[x],1,1,n);
}
LL getsum(int x)
{
	LL ans=0;
	int tx=top[x];
	while(tx!=0)
	{
		ans=ans+cx(pos[tx],pos[x],1,1,n,0);
		x=fa[tx];
		tx=top[x];
	}
	return ans+cx(pos[0],pos[x],1,1,n,0);
}
int main()
{
	int q,i,j,l,r,z;
	scanf("%d%d",&n,&q);
	for(i=1;i<n;i++)
	{
		scanf("%d",&fa[i]);
		G[fa[i]].push_back(i);
	}
	dfs1(0);
	dfs2(0,0);
	for(i=1;i<=q;i++)
	{
		scanf("%d%d%d",&l,&r,&z);
		if(l>0)
		{
			A[l-1].push_back(z);
			N[l-1].push_back(i);
		}
		A[r].push_back(z);
		N[r].push_back(i);
	}
	for(i=0;i<n;i++)
	{
		update(i);
		for(j=0;j<A[i].size();j++)
			Q[N[i][j]][++p[N[i][j]]]=getsum(A[i][j]);
	}
	for(i=1;i<=q;i++)
		printf("%lld\n",jdz(Q[i][1]-Q[i][2])%MOD);
	return 0;
}
注意最后求答案时Q[i][j]不能是mod过的,否则会导致错误,所以Q[i][j]需定义成long long类型

或者Q[i][j]是mod过的也行,但需要记录这个Q[i][j]属于ans[0,l-1]还是ans[0,r]

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值