208E.Blood Cousins(离线+倍增LCA+树上启发式合并)

给出一个树。

每次询问第x点有多少y代表亲。

两个点互为y代表亲当且仅当它们的第y个祖先相同。

题解:

每个点的y代表亲的答案就是:

先向上找节点的第y个祖先,这个祖先子树内的第dep+y层节点总数就是答案。

那么就可以先对询问离线,然后用倍增LCA找到每个点的第y层祖先,记录dep+y和询问编号这两个参数,树上启发式合并。

#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+100;
vector<int> g[maxn];
int f[maxn];//表示当前节点子树内,深度为i的节点的数量
int L[maxn],R[maxn],id[maxn];
int dep[maxn];
int size[maxn];
int tot;
int son[maxn];
int n,m;
int ans[maxn];

vector<pair<int,int> > q[maxn];

void dfs1 (int x,int pre) {
	dep[x]=dep[pre]+1;
	size[x]=1;
	L[x]=++tot;
	id[tot]=x;
	for (int y:g[x]) {
		if (y==pre) continue;
		dfs1(y,x);
		size[x]+=size[y];
		if (size[son[x]]<size[y]) son[x]=y;
	}
	R[x]=tot;
} 
void cal (int x,int pre) {
	f[dep[x]]++;
	for (int y:g[x]) {
		if (y==son[x]||y==pre) continue;
		for (int j=L[y];j<=R[y];j++) {
			int z=id[j];
			f[dep[z]]++;
		}
	}
	for (pair<int,int> y:q[x]) {
		ans[y.second]=max(0,f[y.first]-1);
	}
}
void dfs2 (int x,int pre,int kp) {
	for (int y:g[x]) {
		if (y==son[x]||y==pre) continue;
		dfs2(y,x,0);
	}
	if (son[x]) {
		dfs2(son[x],x,1);
	}
	cal(x,pre);
	if (!kp) for (int i=L[x];i<=R[x];i++) f[dep[id[i]]]=0;
}

int h[maxn];
int father[30][maxn];
void dfs (int x) {
	for (int y:g[x]) {
		if (y==father[0][x]) continue;
		h[y]=h[x]+1;
		father[0][y]=x;
		dfs(y);
	}
}
int main () {
	scanf("%d",&n);
	for (int i=1;i<=n;i++) {
		int x;
		scanf("%d",&x);
		g[x].push_back(i);
	}
	dfs(0);
	dfs1(0,-1);
	for (int i=1;i<=20;i++) for (int j=0;j<=n;j++) {
		father[i][j]=father[i-1][father[i-1][j]];
	}
	scanf("%d",&m);
	for (int i=1;i<=m;i++) {
		int x,y;
		scanf("%d%d",&x,&y);
		
		int tt=h[x]-y;
		//printf("%d %d

",h[x],y);
		if (tt<=0) continue;
		for (int i=20;i>=0;i--) {
			if (h[x]-tt>>i) x=father[i][x];
		}
		//printf("%d %d %d

",x,dep[x],y);
		q[x].push_back(make_pair(dep[x]+y,i));
	}
	dfs2(0,-1,1);
	for (int i=1;i<=m;i++) printf("%d ",ans[i]);
}
原文地址:https://www.cnblogs.com/zhanglichen/p/14610655.html