1. 程式人生 > 實用技巧 >【P5903】【模板】樹上 k 級祖先

【P5903】【模板】樹上 k 級祖先

題目

題目連結:https://www.luogu.com.cn/problem/P5903
給定一棵 \(n\) 個點的有根樹。

\(q\) 次詢問,第 \(i\) 次詢問給定 \(x_i, k_i\),要求點 \(x_i\)\(k_i\) 級祖先。

思路

長剖模板題。
長鏈剖分是按照子樹內最長的鏈來樹剖。在求樹上 \(k\) 級祖先時,可以做到 \(O(n\log n)\) 預處理,單次 \(O(1)\) 查詢。
首先我們 dfs 一遍,求出每一個節點的 \(2^k\) 級祖先,並長剖。對於每一條長鏈的頂端節點,假設這條長鏈長度為 \(d\),那麼在這個節點記錄從這個節點開始,往上 \(d\)

級祖先,以及按照長鏈往下 \(d\) 級子孫。
詢問時,我們先往 \(x\) 上跳 \(2^{k'}\) 級祖先,滿足 \(2^{k'}\leq k\) 並且儘量大。根據長鏈剖分的性質,這個點所在長鏈長度一定不小於 \(2^{k'}\)
由於 \(k-2^{k'}\) 一定小於 \(2^{k'}\),所以我們在這條鏈的頂端也一定記錄了 \(x\)\(k\) 級祖先的資訊。所以直接跳到這條長鏈的頂端,根據剩餘步數選擇往下或往上跳若干步即可。
時間複雜度 \(O(n\log n+Q)\)

程式碼

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned int uint;

const int N=500010,LG=20;
int n,Q,rt,last,tot,lg[N],head[N],maxd[N],son[N],dep[N],top[N],f[N][LG+1];
uint seed;
ll ans;
vector<int> up[N],down[N];

inline uint get(uint x) {
	x ^= x << 13;
	x ^= x >> 17;
	x ^= x << 5;
	return seed = x; 
}

struct edge
{
	int next,to;
}e[N];

void add(int from,int to)
{
	e[++tot]=(edge){head[from],to};
	head[from]=tot;
}

void dfs1(int x)
{
	dep[x]=dep[f[x][0]]+1;
	for (int i=1;i<=LG;i++)
		f[x][i]=f[f[x][i-1]][i-1];
	for (int i=head[x];~i;i=e[i].next)
	{
		int v=e[i].to;
		if (v!=f[x][0])
		{
			dfs1(v);
			maxd[x]=max(maxd[x],maxd[v]+1);
			if (maxd[v]>maxd[son[x]]) son[x]=v;
		}
	}
}

void dfs2(int x,int tp)
{
	top[x]=tp;
	if (x==tp)
	{
		for (int i=x,j=0;j<=maxd[x];j++,i=f[i][0])
			up[x].push_back(i);
		for (int i=x,j=0;j<=maxd[x];j++,i=son[i])
			down[x].push_back(i);
	}
	if (son[x]) dfs2(son[x],tp);
	for (int i=head[x];~i;i=e[i].next)
	{
		int v=e[i].to;
		if (v!=f[x][0] && v!=son[x])
			dfs2(v,v);
	}
}

int query(int x,int k)
{
	if (!k) return x;
	x=f[x][lg[k]]; k-=(1<<lg[k]);
	k-=dep[x]-dep[top[x]]; x=top[x];
	if (k>=0) return up[x][k];
		else return down[x][-k];
}

int main()
{
	memset(head,-1,sizeof(head));
	scanf("%d%d",&n,&Q);
	scanf("%u",&seed);
	for (int i=1;i<=n;i++)
	{
		scanf("%d",&f[i][0]);
		if (!f[i][0]) rt=i;
		add(f[i][0],i);
	}
	for (int i=2;i<=n;i++)
		lg[i]=lg[i>>1]+1;
	dfs1(rt); dfs2(rt,rt);
	for (int i=1;i<=Q;i++)
	{
		int x=(get(seed)^last)%n+1;
		int k=(get(seed)^last)%dep[x];
		last=query(x,k);
		ans^=1LL*i*last;
	}
	printf("%lld",ans);
	return 0;
}