【离线操作】【树链剖分】【LNOI2014】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的最近公共祖先的深度之和)

【输入】
第一行2个整数n q。

接下来n-1行,分别表示点1到点n-1的父节点编号。

接下来q行,每行3个整数l r z。

【输出】
输出q行,每行表示一个询问的答案。每个答案对201314取模输出

【样例输入】
5 2
0
0
1
1
1 4 3
1 4 2
【】样例输出】
8
5
【提示】
共5组数据,n与q的规模分别为10000,20000,30000,40000,50000。

【思路】

这道题的做法不得不说很巧妙。
我们可以构造一个等价问题。如果我们把u到根节点经过的每一个点的权值加一,那么我们可以发现v到根节点经过的点的权值之和就是dep[lca(u,v)]。这是一个十分优美的性质,因为它不仅满足区间加法,甚至满足区间减法。因此我们对于每一个询问l,r,z,我们可以利用前缀和思想得到ans[l,r,z]=ans[1,r,z]-ans[1,l-1,z]。那么现在的问题就只剩下如何得到z到根节点的权值和以及修改节点到1的权值。显然,修改和维护路径信息,我们就使用树链剖分+线段树了。
代码:

#include<cstdio>
#include<iostream>
#include<string>
#include<cstring>
#include<algorithm>
#include<queue>
#define re register
#define LL long long
#define lc (p<<1)
#define rc (p<<1|1)
#define len(p) (t[p].r-t[p].l+1)
using namespace std;
long long n,m,a,b,c;
struct node{
	long long u,v;
}e[200001];
struct tree{
	long long l,r;
	long long sum;
	long long laz;
}t[400001];
long long f[100001];
long long nxp[200001];
long long cnt=0,tot=0;
inline void add(long long u,long long v)
{
	e[++cnt].u=u;
	e[cnt].v=v;
	nxp[cnt]=f[u];
	f[u]=cnt;
}
long long son[100001];
long long siz[100001];
long long dep[100001];
long long fa[100001];
long long top[100001];
long long seg[100001];
long long rev[100001];
void dfs1(long long u,long long ff)
{
	dep[u]=dep[ff]+1;
	fa[u]=ff;
	siz[u]=1;
	for(long long re i=f[u];i;i=nxp[i])
	{
		long long v=e[i].v;
		if(dep[v])continue;
		dfs1(v,u);
		siz[u]+=siz[v];
		if(siz[v]>siz[son[u]])son[u]=v;
	}
}
void dfs2(long long u)
{
	if(son[u])
	{
		seg[son[u]]=++tot;
		rev[tot]=son[u];
		top[son[u]]=top[u];
		dfs2(son[u]);
	}
	for(long long re i=f[u];i;i=nxp[i])
	{
		long long v=e[i].v;
		if(top[v])continue;
		seg[v]=++tot;
		rev[tot]=v;
		top[v]=v;
		dfs2(v); 
	}
}
void pushup(long long p){t[p].sum=t[lc].sum+t[rc].sum;}
void pushnow(long long p,long long v){t[p].laz+=v;t[p].sum+=len(p)*v;}
void pushdown(long long p)
{
	if(t[p].laz)
	{
		pushnow(lc,t[p].laz);
		pushnow(rc,t[p].laz);
		t[p].laz=0;
	} 
}
void build(long long p,long long l,long long r)
{
	t[p].l=l;
	t[p].r=r;
	if(l==r)return;
	long long mid=(l+r)>>1;
	build(lc,l,mid);
	build(rc,mid+1,r);
}
struct que{
	long long z,opt,id;
	long long sum[2];
}q[100001];
vector<que>p[50001];
void change(long long p,long long ql,long long qr)
{
	long long l=t[p].l;
	long long r=t[p].r;
	if(ql<=l&&r<=qr)
	{
		pushnow(p,1);
		return;
	}
	pushdown(p);
	long long mid=(l+r)>>1;
	if(ql<=mid)change(lc,ql,qr);
	if(qr>mid)change(rc,ql,qr);
	pushup(p);
}
long long sum=0;
void ask(long long p,long long ql,long long qr)
{
	long long l=t[p].l;
	long long r=t[p].r;
	if(ql<=l&&qr>=r)
	{
		sum+=t[p].sum;
		return;
	}
	pushdown(p);
	long long mid=(l+r)>>1;
	if(ql<=mid)ask(lc,ql,qr);
	if(qr>mid)ask(rc,ql,qr);
}
void update(long long x)
{
	long long fx=top[x];
	while(fx!=1)
	{
		change(1,seg[fx],seg[x]);
		x=fa[fx];
		fx=top[x];
	}
	change(1,seg[1],seg[x]);
}
void query(long long x)
{
	sum=0;
	long long fx=top[x];
	while(fx!=1)
	{
		ask(1,seg[fx],seg[x]);
		x=fa[fx];
		fx=top[x];
	}
	ask(1,seg[1],seg[x]);
}
int main()
{
	scanf("%lld%lld",&n,&m);
	for(long long re i=2;i<=n;i++)
	{
		scanf("%lld",&a);a++;
		add(a,i);
		add(i,a);
	}
	tot=top[1]=dep[1]=rev[1]=seg[1]=1;
	build(1,1,n);
	dfs1(1,0);
	dfs2(1);
	for(long long re i=1;i<=m;i++)
	{
		long long l,r;
		scanf("%lld%lld%lld",&l,&r,&c);l++,r++,c++;
		q[i].z=c;
		q[i].id=i;
		q[i].opt=0;
		p[l-1].push_back(q[i]);
		q[i].opt=1;
		p[r].push_back(q[i]);
	}
	for(long long re i=0;i<=n;i++)
	{
		if(i>0)
		update(i);
		for(long long re j=0;j<p[i].size();j++)
		{
			query(p[i][j].z);
			q[p[i][j].id].sum[p[i][j].opt]=sum;
		}
	}
	for(long long re i=1;i<=m;i++)printf("%lld\n",(q[i].sum[1]-q[i].sum[0])%201314);
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值