洛谷4211 LNOI2014 LCA 树剖 主席树

题目链接

题意:
给你一个 n n n个点的树,有 q q q次询问,每次给你一个区间和一个点 x x x,问你 x x x这个点和区间里每一个点的lca的深度之和。 n , q < = 50000 n,q<=50000 n,q<=50000

题解:
这个题我的代码在BZOJ上RE了,不知道为什么原因,但是在洛谷上过了,不想调了。于是就不挂BZOJ的标签了。

做法是,你一般来说,要做一个区间的点不好搞,这个题又不是很好用虚树搞,(但是似乎确实有虚树的做法,而且可能还是在线的),那么我们考虑用主席树这种可以前缀相减的数据结构来维护区间问题。

我们发现,我们没法枚举每一个数,但是这个深度我们可以变成路径上的点数。我们对于两个点,求它们LCA的深度可以把一个点到根的路径上的每一个点都加1,然后查询另一个点到根的路径上的权值和。这个转化是这个题的关键。

链上的加法和查询可以用树链剖分来做,而一个区间的点可以用主席树来搞。这里主席树区间的标记pushdown的话比较难搞,不知道能不能写,我写了一下发现写炸了。于是采用的方法是标记永久化,就是不去pushdown。修改的时候记录每个区间应该增加多少,查询的时候一路记录以及经过的标记,在原来的基础上再加上标记之和乘区间长度,差不多就是这个思想。

这样做是在线的。

时间复杂度 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)

代码:

#include <bits/stdc++.h>
using namespace std;

int n,m,fa[500010],dep[500010],hed[500010],cnt,root[500010],shu;
int sz[500010],son[500010],tp[500010],ys[500010],yss[500010];
const long long mod=201314;
struct node
{
	int to,next;
}a[200010];
struct tree
{
	int l,r;
	long long sz,tag;
}tr[1000010];
inline int read()
{
	int x=0;
	char s=getchar();
	while(s>'9'||s<'0')
	s=getchar();
	while(s>='0'&&s<='9')
	{
		x=x*10+s-'0';
		s=getchar();
	}
	return x;
}
inline void add(int from,int to)
{
	a[++cnt].to=to;
	a[cnt].next=hed[from];
	hed[from]=cnt;
}
inline void dfs(int x)
{
	sz[x]=1;
	for(int i=hed[x];i;i=a[i].next)
	{
		int y=a[i].to;
		if(y==fa[x])
		continue;
		dep[y]=dep[x]+1;
		dfs(y);
		sz[x]+=sz[y];
		if(sz[y]>sz[son[x]])
		son[x]=y;
	}
}
inline void dfs2(int x,int top)
{
	tp[x]=top;
	ys[x]=++cnt;
	yss[cnt]=x;
	if(son[x])
	{
		dfs2(son[x],top);
		for(int i=hed[x];i;i=a[i].next)
		{
			int y=a[i].to;
			if(y==fa[x]||y==son[x])
			continue;
			dfs2(y,y);
		}
	}
}
inline void build(int &rt,int l,int r)
{
	if(!rt)
	rt=++cnt;
	if(l==r)
	return;
	int mid=(l+r)>>1;
	build(tr[rt].l,l,mid);
	build(tr[rt].r,mid+1,r);
}
inline void update(int &rt,int l,int r,int le,int ri)
{
	if(l>r||le>ri)
	return;
	tr[++cnt]=tr[rt];
	rt=cnt;
	tr[rt].sz+=ri-le+1;
	if(le==l&&r==ri)
	{
		tr[rt].tag++;
		return;
	}
	int mid=(l+r)>>1;
	if(ri<=mid)
	update(tr[rt].l,l,mid,le,ri);
	else if(mid+1<=le)
	update(tr[rt].r,mid+1,r,le,ri);
	else
	{
		update(tr[rt].l,l,mid,le,mid);
		update(tr[rt].r,mid+1,r,mid+1,ri);
	}
}
inline void change(int x,int y)
{
	int ji=y;
	int f1=tp[x],f2=tp[y];
	while(f1!=f2)
	{
		if(dep[f1]<dep[f2])
		{
			swap(f1,f2);
			swap(x,y);
		}
		update(root[ji],1,n,ys[f1],ys[x]);
		x=fa[f1];
		f1=tp[x];
	}
	if(dep[x]<dep[y])
	update(root[ji],1,n,ys[x],ys[y]);
	else
	update(root[ji],1,n,ys[y],ys[x]);
}
inline long long query(int rt,int l,int r,int le,int ri,long long tag)
{
	if(l>r||le>ri)
	return 0;
	if(le==l&&r==ri)
	return tr[rt].sz+tag*1ll*(r-l+1);	
	int mid=(l+r)>>1;
	if(ri<=mid)
	return query(tr[rt].l,l,mid,le,ri,tag+tr[rt].tag);
	else if(mid+1<=le)
	return query(tr[rt].r,mid+1,r,le,ri,tag+tr[rt].tag);
	else
	return query(tr[rt].l,l,mid,le,mid,tag+tr[rt].tag)+query(tr[rt].r,mid+1,r,mid+1,ri,tag+tr[rt].tag);
}
inline long long find(int rt,int x,int y)
{
	int f1=tp[x],f2=tp[y];
	long long ans=0;
	while(f1!=f2)
	{
		if(dep[f1]<dep[f2])
		{
			swap(f1,f2);
			swap(x,y);
		}
		ans+=query(root[rt],1,n,ys[f1],ys[x],0);
		x=fa[f1];
		f1=tp[x];
	}
	if(dep[x]<dep[y])
	ans+=query(root[rt],1,n,ys[x],ys[y],0);
	else
	ans+=query(root[rt],1,n,ys[y],ys[x],0);
	return ans;
}
int main()
{
	n=read();
	m=read();
	for(int i=2;i<=n;++i)
	{
		fa[i]=read()+1;
		add(i,fa[i]);
		add(fa[i],i);
	}
	cnt=0;
	dep[1]=1;
	dfs(1);
	dfs2(1,1);
	cnt=0;
	build(root[0],1,n);
	for(int i=1;i<=n;++i)
	{
		root[i]=root[i-1];
		change(1,i);
	}		
	for(int i=1;i<=m;++i)
	{
		int x=read()+1,y=read()+1,z=read()+1;
		printf("%lld\n",(find(y,1,z)-find(x-1,1,z))%mod);
	}
	return 0; 
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值