BZOJ3626 LCA 【树链剖分+线段树】

题目描述:

给出一个n个节点的有根树(编号为0到n-1,根节点为0)。一个点的深度定义为这个节点到根的距离+1。
设dep[i]表示点i的深度,LCA(i,j)表示i与j的最近公共祖先。
有q次询问,每次询问给出l r z,求 ∑ l &lt; = i &lt; = r d e p [ L C A ( i , z ) ] \sum_{l&lt;=i&lt;=r}dep[LCA(i,z)] l<=i<=rdep[LCA(i,z)]
(即,求在[l,r]区间内的每个节点i与z的最近公共祖先的深度之和)
n,q<=50000

题目分析:

高超的技巧:
求两点x,y的lca的深度,可以把x到根的路径上的点全部打上标记,lca的深度就是y往上遇到的第一个打了标记的点(记作z)的深度,而从z往上的每个点都是打了标记的,所以等价于y往上直到根所遇到的打了标记的点的个数。

这道题求区间,显然不能每次把区间内的每个点都往上加一遍,考虑差分后离线,把询问拆成 [1,l-1],z 和 [1,r],z ,从1到n加点,加入点i时把i到根的路径上的点权+1,询问[1,i],z时统计z到根的路径和即可

树上路径加和路径求和可以用树链剖分,dfs一下可以把一条重链上的点划成一段连续的区间,就可以用线段树维护每条重链上的信息了。

#include<cstdio>
#include<algorithm>
#define maxn 50005
using namespace std;
const int mod = 201314;
int n,m,pos[maxn],pt,ans[maxn],fa[maxn];
int siz[maxn],son[maxn],top[maxn];
int fir[maxn],nxt[maxn],to[maxn],tot;
inline void line(int x,int y){nxt[++tot]=fir[x];fir[x]=tot;to[tot]=y;}
struct Q{
	int r,z,flg,id;
	Q(){}
	Q(int _r,int _z,int _flg,int _id){r=_r,z=_z,flg=_flg,id=_id;}
	bool operator < (const Q &p)const{return r<p.r;}
}q[maxn<<1];
struct segment_tree{
	int s[maxn<<2],tag[maxn<<2];
	void pushdown(int i,int l,int r)
	{
		if(!tag[i]) return;
		int mid=(l+r)>>1;
		s[i<<1]=(s[i<<1]+1ll*(mid-l+1)*tag[i])%mod;
		s[i<<1|1]=(s[i<<1|1]+1ll*(r-mid)*tag[i])%mod;
		tag[i<<1]+=tag[i],tag[i<<1|1]+=tag[i];
		tag[i]=0;
	}
	void insert(int i,int l,int r,int x,int y)
	{
		if(x<=l&&r<=y) {s[i]=(s[i]+r-l+1)%mod,tag[i]++;return;}
		pushdown(i,l,r);
		int mid=(l+r)>>1;
		if(x<=mid) insert(i<<1,l,mid,x,y);
		if(y>mid) insert(i<<1|1,mid+1,r,x,y);
		s[i]=(s[i<<1]+s[i<<1|1])%mod;
	}
	int query(int i,int l,int r,int x,int y)
	{
		if(x<=l&&r<=y) return s[i];
		pushdown(i,l,r);
		int mid=(l+r)>>1,ret=0;
		if(x<=mid) ret+=query(i<<1,l,mid,x,y);
		if(y>mid) ret+=query(i<<1|1,mid+1,r,x,y);
		return ret;
	}
}seg;
void dfs1(int u)
{
	siz[u]=1;
	for(int i=fir[u];i;i=nxt[i]){
		dfs1(to[i]);
		siz[u]+=siz[to[i]];
		if(!son[u]||siz[son[u]]<siz[to[i]]) son[u]=to[i];
	}
}
void dfs2(int u)
{
	pos[u]=++pt;
	if(son[u]) top[son[u]]=top[u],dfs2(son[u]);
	for(int i=fir[u];i;i=nxt[i]) if(to[i]!=son[u]) top[to[i]]=to[i],dfs2(to[i]);
}
void update(int x)
{
	while(top[x]!=top[0]){
		seg.insert(1,1,n,pos[top[x]],pos[x]);
		x=fa[top[x]];
	}
	seg.insert(1,1,n,pos[top[0]],pos[x]);
}
int solve(int x){
	int s=0;
	while(top[x]!=top[0]){
		s+=seg.query(1,1,n,pos[top[x]],pos[x]);
		x=fa[top[x]];
	}
	return s+seg.query(1,1,n,pos[top[0]],pos[x]);
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<n;i++) scanf("%d",&fa[i]),line(fa[i],i);
	for(int i=1,x,y,z;i<=m;i++) scanf("%d%d%d",&x,&y,&z),q[i*2-1]=Q(x-1,z,-1,i),q[i*2]=Q(y,z,1,i);
	sort(q+1,q+1+2*m);
	dfs1(0),top[0]=0,dfs2(0);
	for(int i=1,j=-1;i<=2*m;i++){
		if(q[i].r==-1) continue;
		while(j<q[i].r) update(++j);
		ans[q[i].id]=(ans[q[i].id]+solve(q[i].z)*q[i].flg)%mod;
	}
	for(int i=1;i<=m;i++) printf("%d\n",(ans[i]+mod)%mod);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值