Luogu_P6374 树上询问

本文详细介绍了如何处理树上的询问问题,特别是Luogu_P6374题目的解法。通过建立无根树,并利用 Lowest Common Ancestor (LCA) 函数,针对每个询问(a, b, c),判断c是否在a和b的路径上,从而计算满足条件的节点i的数量。文章探讨了不同情况的解决方案,包括LCA性质的运用和不同情况下的节点计数策略。" 109498585,8162093,时间显式与时间隐式计算程序解析,"['数值计算', '科学计算', '传热学', '编程', '物理模型']

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

树上询问

链接

Luogu_P6374 树上询问

题目描述

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

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

输入格式

第一行 2 2 2个数,为 n n n q q q

接下来 n − 1 n−1 n1行,每行 2 2 2个数,表示树的一条边。

接下来 q q q行,每行 3 3 3个数,为 ( a , b , c ) (a,b,c) (a,b,c)

输出格式

q q q行,每行一个数,为对于每个三元组的 i i i的个数。

输入输出样例

输入 #1
10 5
1 2
1 3
2 4
2 5
2 10
5 6
3 7
7 8
7 9
4 6 2
4 10 1
6 8 3
9 10 2
4 10 5
输出 #1
7
0
1
4
0
输入 #2
5 3
1 3
1 5
3 4
3 2
5 2 3
5 2 1
2 4 5
输出 #2
2
1
0
输入 #3
20 10
1 2
1 3
1 4
2 5
2 6
3 10
4 13
4 14
6 7
6 8
10 11
4 15
4 16
8 9
11 12
16 17
16 18
16 19
17 20
15 19 16
1 12 1
20 20 20
7 7 8
1 8 3
5 20 2
2 9 6
9 12 1
9 12 2
9 12 3
输出 #3
4
16
20
0
0
5
2
10
2
1

数据范围
本题按子任务测试:
s u b t a s k 1 ( 20 p t s ) s u b t a s k 1 ( 20 p t s ) subtask1 (20pts)subtask1(20pts) subtask1(20pts)subtask1(20pts) 1 ≤ n ≤ 1000 1 \leq n \leq1000 1n1000 1 ≤ q ≤ 1 ≤ q ≤ 500500 1 \leq q \leq1≤q≤ 500500 1q1q500500
s u b t a s k 2 ( 15 p t s ) s u b t a s k 2 ( 15 p t s ) subtask2 (15pts)subtask2(15pts) subtask2(15pts)subtask2(15pts) 1 ≤ n ≤ 1 0 5 1 \leq n \leq10^{5} 1n105 1 ≤ q ≤ 1 0 5 1 \leq q \leq10^{5} 1q105,树退化成链 。
s u b t a s k 3 ( 25 p t s ) s u b t a s k 3 ( 25 p t s ) subtask3 (25pts)subtask3(25pts) subtask3(25pts)subtask3(25pts) 1 ≤ n ≤ 5 × 1 0 5 1 \leq n \leq5 \times 10^{5} 1n5×105 1 ≤ q ≤ 1 0 5 1 \leq q \leq10^{5} 1q105,数据不随机 。
s u b t a s k 4 ( 40 p t s ) s u b t a s k 4 ( 40 p t s ) subtask4 (40pts)subtask4(40pts) subtask4(40pts)subtask4(40pts) 1 ≤ n ≤ 5 × 1 0 5 1 \leq n \leq5 \times 10^{5} 1n5×105 1 ≤ q ≤ 2 × 1 0 5 1 \leq q \leq 2 \times10^{5} 1q2×105

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

注:数据强度不高,不必卡常与快读快输。

思路

这是大家讨论出来的思路。

对于树上的任意节点 a a a b b b,连通它们的路径有且仅有一条。由于题目给出的是一棵无根树,所以连通 a a a b b b的路径上的任意一点(包括 a a a b b b)都有可能是 L C A ( a , b ) LCA(a,b) LCA(a,b)的值。

很显然,节点 L C A ( a , b ) LCA(a,b) LCA(a,b)拥有的包含 a a a b b b节点的子树上的节点,除了在连通 a a a b b b的路径上的节点以外,都不可能作为根,否则 L C A ( a , b ) LCA(a,b) LCA(a,b)的值将永远会是 a a a b b b,不会是路径上的其他点。

测试数据#2图

如图,连通节点 1 1 1 2 2 2的路径为 1 − 3 − 2 1-3-2 132,则 L C A ( 1 , 2 ) LCA(1,2) LCA(1,2)的值可能为 1 1 1 2 2 2 3 3 3。由图可得,成为根节点后能够使 L C A ( 1 , 2 ) LCA(1,2) LCA(1,2)的值为 1 1 1 2 2 2 3 3 3的节点有 1 1 1 2 2 2 3 3 3 4 4 4,并且不可能是 5 5 5,因为如果以 5 5 5为根节点的话, L C A ( 1 , 2 ) LCA(1,2) LCA(1,2)的值将为 1 1 1,不可能为 2 2 2 3 3 3

所以,我们要做的就是对给出的每个询问,判断节点 c c c是否在连通节点 a a a b b b的路径上。若不在,则答案为 0 0 0;若在,则答案为除去包含节点 a a a和包含节点 b b b的节点 c c c的子树后剩余节点的数目。

语言表述的方法有了,但怎么用计算机实现呢?

或许,我们可以像语言表述的那样,对于每个输入的 ( a , b , c ) (a,b,c) (a,b,c),找出连通 a a a b b b的路径,判断 c c c是否在这条路径上,再根据判断的结果求出答案。

不过,看看数据规模: 5 × 1 0 5 × 2 × 1 0 5 = 1 0 11 5 \times 10^{5} \times 2 \times 10^{5} =10^{11} 5×105×2×105=1011。很显然是不可能的。

这个时候,我们的 L C A LCA LCA函数就发挥作用了。

首先,我们先任意设置一个节点为根节点,并按照它制作一份 S T ST ST表,同时记录每个节点的深度。

接着,我们对节点 a a a b b b c c c L C A LCA LCA函数的运算。如果节点 c c c在连通 a a a b b b的路径上,那么它们一定满足一个性质,即: l c a ( l , l c a ( a , b ) ) = l c a ( a , b ) lca(l,lca(a,b))=lca(a,b) lca(l,lca(a,b))=lca(a,b),证明请自己推。

然后,我们需要分三种情况讨论。

  1. 第一种, l c a ( a , b ) = c lca(a,b)=c lca(a,b)=c,答案等于总的节点数减去包含包含节点 a a a b b b的节点 c c c的子树;
  2. 第二种, l c a ( a , c ) = c lca(a,c)=c lca(a,c)=c,答案等于节点 c c c及其子树的节点总数减去包含包含节点 a a a的节点 c c c的子树(不用做有关节点 b b b及其子树的运算是因为节点 c c c及其子树的节点总数与节点 b b b及其子树的节点总数互不影响);
  3. 第二种, l c a ( b , c ) = c lca(b,c)=c lca(b,c)=c,答案等于节点 c c c及其子树的节点总数减去包含包含节点 b b b的节点 c c c的子树;

这样,整个程序就完成了。

代码

#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
const int N=5*100000;
struct str{
	int to,nex;
}e[2*N+1];
int ls[2*N+1],cnt;
int dep[2*N+1],f[2*N+1][21],tot[N+1];
void add(int u,int v)
{
	cnt++;
	e[cnt].to=v;
	e[cnt].nex=ls[u];
	ls[u]=cnt;
}
void bfs(int root)//深度预处理 
{
	int fa,so;
	queue <int> q;
	dep[root]=1;
	q.push(root);
	while(!q.empty())
	{
		fa=q.front();
		q.pop();
		for(int i=ls[fa];i;i=e[i].nex)
		{
			so=e[i].to;
			if(!dep[so])
			{
				f[so][0]=fa;
				dep[so]=dep[fa]+1;
				q.push(so);
			}
		}
	}
}
void ST(int n)//制作树的ST表 
{
	for(int j=1;j<=20;j++)
		for(int i=1;i<=n;i++)
			f[i][j]=f[f[i][j-1]][j-1];
}
int lca(int x,int y)//计算两个点的LCA值 
{
	if(dep[x]>dep[y])
		swap(x,y);
	int d,k,t;
	d=dep[y]-dep[x];
	k=20;
	t=1<<k;
	while(d)
	{
		if(d>=t)
		{
			y=f[y][k];
			d-=t;
		};
		t/=2;
		k--;
	}
	if(x==y)
		return x;
	k=20;
	while(k>=0)
	{
		if(f[x][k]!=f[y][k])
		{
			x=f[x][k];
			y=f[y][k];
		}
		k--;
	}
	return f[x][0];
}
int dfs(int k)//计算每个节点子树上的总的节点数目(包括这个节点本身) 
{
	int sum=1;
	for(int i=ls[k];i;i=e[i].nex)
		if(f[k][0]!=e[i].to)
		{
			tot[e[i].to]=dfs(e[i].to);
			sum+=tot[e[i].to];
		}
	return sum;
}
int val(int so,int fa)//计算a或b所在的子树的节点数目 
{
	int k;
	if(so==fa)
		return 0;
	k=20;
	while(k>=0)
	{
		if(dep[f[so][k]]>dep[fa]) 
			so=f[so][k];
		k--;
	}
	return tot[so];
}
bool check(int a,int b,int c)//判断c是否在连通a、b的路径上 
{
	int l=lca(a,b);
	return lca(l,c)==l;
}
int main()
{
	int n,q,x,y,v,a,b,c,ans;
	scanf("%d%d",&n,&q);
	for(int i=1;i<=n-1;i++)
	{
		scanf("%d%d",&x,&y);
		add(x,y);
		add(y,x);
	}
	bfs(1);
	ST(n);
	tot[1]=dfs(1);
	for(int i=1;i<=q;i++)
	{
		ans=0;
		scanf("%d%d%d",&a,&b,&c);
		if(check(a,b,c)&&(lca(a,b)==c))//分类讨论情况1
			ans=n-val(a,c)-val(b,c);
		else if(check(a,b,c)&&(lca(a,c)==c))//分类讨论情况2
			ans=tot[c]-val(a,c);
		else if(check(a,b,c)&&(lca(b,c)==c))//分类讨论情况3
			ans=tot[c]-val(b,c);
		printf("%d\n",ans);
	}
	return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值