树上询问
链接
题目描述
给定一棵 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 n−1行,每行 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
1≤n≤1000 ,
1
≤
q
≤
1
≤
q
≤
500500
1 \leq q \leq1≤q≤ 500500
1≤q≤1≤q≤500500 。
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}
1≤n≤105,
1
≤
q
≤
1
0
5
1 \leq q \leq10^{5}
1≤q≤105,树退化成链 。
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}
1≤n≤5×105,
1
≤
q
≤
1
0
5
1 \leq q \leq10^{5}
1≤q≤105,数据不随机 。
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}
1≤n≤5×105 ,
1
≤
q
≤
2
×
1
0
5
1 \leq q \leq 2 \times10^{5}
1≤q≤2×105。
对于所有数据: 1 ≤ n ≤ 5 × 1 0 5 1 \leq n \leq5 \times 10^{5} 1≤n≤5×105, 1 ≤ q ≤ 2 × 1 0 5 1 \leq q \leq2 \times 10^{5} 1≤q≤2×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,不会是路径上的其他点。
如图,连通节点 1 1 1、 2 2 2的路径为 1 − 3 − 2 1-3-2 1−3−2,则 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),证明请自己推。
然后,我们需要分三种情况讨论。
- 第一种, l c a ( a , b ) = c lca(a,b)=c lca(a,b)=c,答案等于总的节点数减去包含包含节点 a a a、 b b b的节点 c c c的子树;
- 第二种, 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及其子树的节点总数互不影响);
- 第二种, 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;
}