洛谷6374 树上询问

题目描述

给定一棵 n 个点的无根树,有 q 次询问。

每次询问给一个参数三元组 (a,b,c) ,求有多少个 i 满足这棵树在以 i 为根的情况下 a 和 b 的 LCA 为 c 。

数据范围

对于所有数据: 1 ≤ n ≤ 5 × 1 0 5 1 \leq n \leq 5\times10^{5} 1n5×105 1 ≤ q ≤ 2 × 1 0 5 1 \leq q \leq2 \times 10^{5} 1q2×105

思路

分析题意,显然,如果c在a到b的路径上才有解,否则i的个数肯定为0。而合法的i的数量就等于总节点数分别减去以c的子节点为根的含有a,b的子树的节点数。

如图所示:圈起来的部分都是要减掉的,因为要使lca(a,b)为c,显然a,b都要在以c为根的子树中,而要使c为a,b所在子树的根,所选择的根节点i必然在以c为根的其它子树中。(包括c自己)

在这里插入图片描述
基本思路有了,接下来就考虑如何实现。

首先,先假定任意一个节点为根节点。(一般都是1号节点,不为什么,舒服…)dfs遍历整棵树,并在回溯时求出以每个节点为根的子树的节点数。(这应该不难吧,不懂自己看代码)然后预处理出用来求lca的ST表。(用其他方式的当我没说)

接下来,重头戏来了。以上面那张图为例,分三种情况讨论。(当然,讨论的情况必须满足有解的前提,即c在a到b的路径上)

  1. c就是a,b的lca
    这种情况比较容易判断,就是lca(a,b)=c,但答案的求法有点麻烦。
    根据之前的推理,总节点数已知,只需要求以c的子节点为根的含有a,b的子树的节点数。我们已经预处理出了以所有节点为根的子树的节点数,只需要知道a,b所在子树的根节点是c的哪个子节点就行了。考虑用倍增法求lca时的抬升思想,将a,b分别抬升至离c差一层的深度,这时a、b就是所求的c的子节点。

  2. c在a到lca(a,b)的路径上
    这种情况就比较麻烦。但我们可以这样处理(顺便把大前提如何判断一起讲了):若lca(a,b)与c的lca就等于lca(a,b),根据lca的定义,说明lca(a,b)肯定是c的祖先。
    重点来了,这时判断a和c的lca是谁。若lca(a,c)就等于c,那么c肯定是a的祖先,那么a要想到达c的祖先lca(a,b),必然要经过它自己的祖先c,换言之,c就在a到lca(a,b)的路径上。
    答案的求法思想与上一种情况一样,不过不同的是,在dfs求以c为根的子树节点个数时,它就只包含含有a的子树节点数,而含有b的子树节点数根本不会被统计到。(可自己结合图示理解一下)

  3. c在b到lca(a,b)的路径上
    与第二种情况一模一样,将a改成b就行了。


代码

#include<iostream>
#include<cstdio>
#include<vector>
using namespace std;
const int N=1e6,P=20;
int n,q;
int dep[N],st[N][P+5],size[N];
vector <int> ed[N];
void dfs(int x,int fa)//预处理每个节点的深度 
{
    
    
	dep[x]=dep[fa]+1;
	st[x][0]=fa;
	for(int i=0;i<ed[x].size();i++)
	if(ed[x][i]!=fa)
	dfs(ed[x][i],x);
}
int lca(int x,int y)
{
    
    
	if(dep[x]>dep[y]) swap(x,y);
	int dif=dep[y]-dep[x],step=0;
	while(dif)
	{
    
    
		if(dif&1) y=st[y][step];
		dif>>=1,step++;
	}
	if(x==y) return x;
	step=P+1;
	while(--step>=0)
	if(st[x][step]!=st[y][step])
	x=st[x][step],y=st[y][step];
	return st[x][0];
}
int sum(int x)//求以每个节点为根的子树的节点数 
{
    
    
	for(int i=0;i<ed[x].size();i++)
	if(ed[x][i]!=st[x][0]) size[x]+=sum(ed[x][i]);
	return ++size[x];
}
int query(int x,int y)//查找以c为根的包含a,b的子树的节点数 (不包含c) 
{
    
    
	if(x==y) return 0;
	int step=P+1;
	while(--step>=0)
	if(dep[st[x][step]]>dep[y])
	x=st[x][step];
	return size[x];//最后x会走到c的子节点,即包含a,b的子树的根节点 
}
int main()
{
    
    
	scanf("%d%d",&n,&q);
	for(int i=1;i<n;i++)
	{
    
    
		int x,y;
		scanf("%d%d",&x,&y);
		ed[x].push_back(y),ed[y].push_back(x);
	}
	dfs(1,0);	
	for(int i=1;i<=P;i++)
	for(int j=1;j<=n;j++)
	st[j][i]=st[st[j][i-1]][i-1];
	sum(1);
	for(int i=1;i<=q;i++)
	{
    
    
		int a,b,c;
		scanf("%d%d%d",&a,&b,&c);
		int anc=lca(a,b);
		if(lca(c,anc)!=anc)//c不在以a,b的lca为根的子树上 
		{
    
    
			printf("%d\n",0);
			continue;
		}
		if(lca(a,b)==c)//c就是a,b的lca 
			printf("%d\n",n-query(a,c)-query(b,c));//所有节点数减去a、b所在子树的节点数 
		else if(lca(a,c)==c)//c在a到a,b的lca的路径上 
			printf("%d\n",size[c]-query(a,c));//本该也是所有节点数减去a、b所在子树的节点数,但size[c]统计时就不包含b所在子树的节点数,因此只需要减去a所在子树节点数 
		else if(lca(b,c)==c)//c在b到a,b的lca的路径上 
			printf("%d\n",size[c]-query(b,c));//同上,size[c]减去b所在子树的节点数 
		else printf("%d\n",0);
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_45383207/article/details/113048368