【长链剖分】lxhgww的奇思妙想

【题目描述】
lxhgww 在树上玩耍时,LZX2019 走了过来。lxhgww 突然问道:“我现在的k级祖先是谁?”
LZX2019 答道:“不是我吗?”。接着 lxhgww 就用教主之力让 LZX2019 消失了,现在他转过头准备向你求助。
【输入】
第一行包含一个整数N,表示树的结点数。
接下来N-1行包含两个整数X,Y,表示第X个结点和第Y个结点间有一条边。
接下来1行包含一个整数M,表示询问个数。
接下来M行包含两个整数a,b,则x=alastans,k=blastans。

【输出】
输出包含M行,分别是M个询问的答案。
若没有k级祖先,则输出0

【样例输入】
5
1 2
2 3
1 4
4 5
5
5 0
6 7
4 5
2 1
4 3
【样例输出】
5
1
0
1
1
限制
每个测试点1s,512MB
提示
数据范围:
1 ≤ N ≤300000, M ≤ 1800000
1 ≤ x,k≤ N

【思路】

这道题应该使用长链剖分。
简单介绍一下长链剖分。长链剖分和重链剖分差不多,只是不维护节点最多的儿子,而维护能够向下深度最大的儿子。
分两次dfs,第一次维护每个节点的父节点,向下走的最大深度的儿子,深度,以及向下的最大深度的大小。第二次维护每一条长链的链节点,以及链的长度。
而这样我们将会有一个优雅的性质。一个节点的k级祖先所在链的长度一定大于等于k。
那么我们可以采取以下步骤处理本题:
1.长链剖分,同时倍增维护每个节点的2的幂次的祖先。
2.记链长度为len,数组维护每个链端点向上len长度的祖先和向下len长度的链上节点。
3.对于要询问的点x,我们可以先跳到x的k的最高位的1 的父亲的地方。这样我们可以保证x到达的节点所在链长度一定大于k的最高位的1(由那个优雅的性质可得),也就大于剩下的需要向上走的步数。因此我们只需要在链端点判断一下向上走几步或者向下走几步就可以找到k级祖先了。
代码:

#include<cstdio>
#include<iostream>
#include<vector>
#include<cstring>
#include<algorithm>
#include<queue>
#define re register
#define LL long long
using namespace std;
int n,m,a,b,c;
struct node{
	int u,v;
}e[2000001];
int nxp[2000001];
int f[1000001];
int cnt=0;
vector<int>up[1000001];
vector<int>down[1000001];
inline void add(int u,int v)
{
	e[++cnt].u=u;
	e[cnt].v=v;
	nxp[cnt]=f[u];
	f[u]=cnt;
}
int son[1000001];
int dep[1000001];
int top[1000001];
int mxd[1000001];
int len[1000001];
int d[1000001];
int bin[3000001];
int fa[1000001][25];
void dfs1(int u,int ff)
{
	dep[u]=dep[ff]+1;
	fa[u][0]=ff;
	for(int re i=1;(1<<i)<=dep[u];i++)
		fa[u][i]=fa[fa[u][i-1]][i-1];
	d[u]=dep[u]=dep[ff]+1;
	for(int re i=f[u];i;i=nxp[i])
	{
		int v=e[i].v;
		if(dep[v])continue;
		dfs1(v,u);
		if(d[u]<d[v])son[u]=v,d[u]=d[v];
	}
}
inline int red()
{
    int data=0;int w=1; char ch=0;
    ch=getchar();
    while(ch!='-' && (ch<'0' || ch>'9')) ch=getchar();
    if(ch=='-') w=-1,ch=getchar();
    while(ch>='0' && ch<='9') data=(data<<3)+(data<<1)+ch-'0',ch=getchar();
    return data*w;
}
void dfs2(int u)
{
	len[u]=d[u]-dep[top[u]]+1;
	if(son[u])
	{
		top[son[u]]=top[u];
		dfs2(son[u]);
	}
	for(int re i=f[u];i;i=nxp[i])
	{
		int v=e[i].v;
		if(len[v])continue;
		top[v]=v;
		dfs2(v);
	}
}
void pre1()
{
	for(int re i=0;i<=20;i++)
		for(int re j=0;j<(1<<i);j++)
			bin[(1<<i)+j]=i;
}
void pre2()
{
	for(int re i=1;i<=n;i++)
	{
		if(top[i]!=i)continue;
		int l=0,x=i;
		while(l<len[i]&&x)x=fa[x][0],up[i].push_back(x),++l;
		l=0,x=i;
		while(l<len[i])x=son[x],down[i].push_back(x),++l;
	}
}
int lans=0;
int find(int x,int k)
{
	if(k>=dep[x])return 0;
	if(!k)return x;
	x=fa[x][bin[k]];
	k-=(1<<bin[k]);
	if(!k)return x;
	if(k==dep[x]-dep[top[x]])return top[x];
	if(k>dep[x]-dep[top[x]])
		return up[top[x]][k-(dep[x]-dep[top[x]])-1];
	return down[top[x]][(dep[x]-dep[top[x]])-k-1];
}
int main()
{
	pre1();
	n=red();
	for(int re i=1;i<n;i++)
	{
		a=red();b=red();
		add(a,b);
		add(b,a);
	}
	top[1]=1;
	dfs1(1,0);
	dfs2(1);
	pre2();
	m=red();
	for(int re i=1;i<=m;i++)
	{
		a=red();b=red();
		a^=lans;
		b^=lans;
		lans=find(a,b);
		printf("%d\n",lans);
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值