树上询问

4 篇文章 0 订阅

P6374 「StOI-1」树上询问

题目描述

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

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

输入格式
第一行2个数,为 n 和 q 。

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

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

输出格式
共 q 行,每行一个数,为对于每个三元组的 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

说明/提示
样例2解释

第一个查询的 ii 为 34。

第二个查询的 ii 为 1

数据范围

在这里插入图片描述

思路:

由于本题并未指定一个根节点,且节点个数却到达了“5*105”个
so,不可能去枚举每一个点作为根节点
so,必须自行定义一个根节点,在进行判断
至于根节点可自行选择

在这里插入图片描述
对于本图而言以“9”为“a”,以“4”为“b”,以“2”为“c”

显而易见 qwq最后可以为根节点的则有“2”+“10”,“5”,“6”

其实可以简化成:

在这里插入图片描述
再转回第一幅图,则可知

其实最后只用处理“c”的子树即可

也就是将“c”的所有子树-“b”所在的“c”的那个子树而非以“b”为根节点的那棵树

就为可能的根节点数…………

你以为完了么???你放屁

其实还有两种情况

即(1):“a”,“b”都为“c”的子树且不在“c”的同一棵子树中

即:
在这里插入图片描述
对于此图,则…………去看代码吧…………

(2):“c”并不在“a”~“b”这条链中

即:
在这里插入图片描述
则直接输出“0”即可…………

至于怎么判断…………自己看代码…………再在草稿本上推算吧…………

#include <algorithm>
#include <iostream>
#include <cstring>
#include <vector>
#include <cstdio>
#include <queue>
#include <cmath>
#define LL long long
using namespace std;

const int N=5*1e5+100,M=1001;
int n,m,u,v,a,b,c,f[N][30],dis[N],cnt[N],high[N];
vector < int > q[N];
void dfs ( int rt,int fa,int h )
{
	cnt[rt]=1;
	f[rt][0]=fa;
	high[rt]=h++;
	int len=q[rt].size ( );
	for ( int i=0;i<len;i++ )
	{
		int to=q[rt][i];
		if ( to!=fa )
		{
			dfs ( to,rt,h );
			cnt[rt]+=cnt[to];            //看每个数的节点数+其本身…………
		}
	}
}
int lca ( int x,int y )
{
	if ( high[x]>high[y] ) swap ( x,y );
	int c=high[y]-high[x],k=19,m=1<<k;
	while ( c )
	{
		if ( c>=m ) y=f[y][k],c-=m;
		m/=2,k--;
	}
	if ( x==y ) return x;
	k=19;
	while ( k>=0 ) { if ( f[x][k]!=f[y][k] ) x=f[x][k],y=f[y][k];k--; }
	return f[x][0];
}
int find ( int x,int y )          //找出从x-->y的x在的那个子树的根节点
{
	if ( x==y ) return 0;
	for ( int i=19;i>=0;i-- ) if ( high[f[x][i]]>high[y] ) x=f[x][i];
	return cnt[x];
}

int main ( )
{
	scanf ( "%d %d",&n,&m );
	for ( int i=1;i<n;i++ )
	{
		scanf ( "%d %d",&u,&v );
		q[u].push_back ( v ),q[v].push_back (u);
	}
	dfs ( 1,0,1 );
	for ( int j=1;j<=19;j++ ) for ( int i=1;i<=n;i++ ) f[i][j]=f[f[i][j-1]][j-1];
	while ( m-- )
	{
		scanf ( "%d %d %d",&a,&b,&c );
		int ca=lca ( a,c ),cb=lca ( b,c ),ab=lca ( a,b );
		int fa=ca==c?find ( a,c ):0,fb=cb==c?find ( b,c ):0;
		if ( lca (ab,c)!=ab ) printf ( "0\n" );                   //如(2)
		else if ( ab==c ) printf ( "%d\n",cnt[1]-fa-fb );         //如(1)
		else if ( ca==c) printf ( "%d\n",cnt[c]-fa );             //如图1
		else if ( cb==c) printf ( "%d\n",cnt[c]-fb );             //本质和图1相同
		else printf ( "0\n" );                                    //如(2)
	}
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值