树论
倍增法求LCA
【LCA】LuoguP4281 紧急集合
对一棵树,边\((x_i,y_i)\)表示\(x_i,y_i\)之间存在双向边,边权为1,对于\(Q\)个询问,每个询问包含一个三元组\((x,y,z)\),表示三个人分别所处的树结点编号,求这三个位置的人移动到某个相同点经过的边权和的最小值,给出此最佳集结点与边权和最小值。
此处证明:最短边权和为此情况:集结点为某两个起始点的LCA。
考虑\((x,y)\)的情况,最佳集结点为\(P:x\to y\)路径上任何一个点,\(z\)需要以最短路径走到\(P\)上,若\(lca(x,z)\in P或lca(y,z)\in P\),则\(lca(x,z)或lca(y,z)\)为最佳集结点,若\(lca(x,z),lca(y,z)\notin P\),则\(lca(x,y)\)为最佳集结点。
参考解答
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76 | #include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
struct edge{int v,next;}a[N];
int depth[N],h[N],tot,fa[N][21],n,m;
struct node{int posi,ans;};
void add(int x,int y)
{
a[++tot]=(edge){y,h[x]};
h[x]=tot;
}
void maketree(int x,int father,int d)
{
fa[x][0]=father,depth[x]=d;
for(int i=h[x];i;i=a[i].next)
{
int v=a[i].v;
if(v==father) continue;
maketree(a[i].v,x,d+1);
}
}
node lca(int x,int y)
{
int ans=0;
if(depth[x]<depth[y]) swap(x,y);
for(int i=20;i>=0;i--)
if(depth[fa[x][i]]>=depth[y])
ans+=(1<<i),x=fa[x][i];
if(x==y) return (node){x,ans};
for(int i=20;i>=0;i--)
if(fa[x][i]!=fa[y][i])
{
ans+=2*(1<<i);
x=fa[x][i],y=fa[y][i];
}
return (node){fa[x][0],ans+2};
}
int main()
{
cin>>n>>m;
int u,v;
for(int i=1;i<n;i++)
{
scanf("%d%d",&u,&v);
add(u,v),add(v,u);
}
maketree(1,0,1);
for(int i=1;i<=20;i++)
for(int j=1;j<=n;j++)
fa[j][i]=fa[fa[j][i-1]][i-1];
int x,y,z;
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&x,&y,&z);
node p1=lca(x,y),p2=lca(y,z),p3=lca(x,z);
node p4=lca(z,p1.posi),p5=lca(x,p2.posi),p6=lca(y,p3.posi);
int ANS=min(p4.ans+p1.ans,
min(p5.ans+p2.ans,p6.ans+p3.ans));
if(ANS==p4.ans+p1.ans)
{
printf("%d %d\n",p1.posi,ANS);
continue;
}
if(ANS==p5.ans+p2.ans)
{
printf("%d %d\n",p2.posi,ANS);
continue;
}
if(ANS==p6.ans+p3.ans)
{
printf("%d %d\n",p3.posi,ANS);
continue;
}
}
return 0;
}
|
【LCA】【树论】LuoguP3398 仓鼠找sugar
【题目大意】对于一棵树\(T\),边\((x_i,y_i)\)表示\(x_i,y_i\)存在双向边,有\(Q\)个询问,对于每个询问给出\((a,b,c,d)\),判断路径\(X:a\to b,Y:c\to d\)是否有交点。
先证明:路径存在交点的充要条件是某路径端点的LCA落在另一条路径上。必要性显然,下证充分性:
假设某一交点为\(P\),
(1)若路径\(a\to b,c\to d\)在过点后同时上升(走向深度更低的点),则此点也为路径公共点\((fa_P)\),转为讨论\(fa_P\);
(2)同时下降则从逆方向看,转为第一种情况;
(3)若无其他情况,明显得证;
(4)若在某点,一条路径上升,一条路径下降,则得证,后者端点的LCA在前者上。
判断方法:对于\(lca(a,b)=p_1,lca(c,d)=p_2\),当\(p_1\in Y\)时一定有\(depth_{p_1}\ge depth_{p_2}\),此时\(lca(c,p_1)=p_1\cup lca(d,p_1)=p_1\)成立。\(p_2\in X\)同理,均不满足则无交点。
参考解答
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71 | #include<bits/stdc++.h>
using namespace std;
const int N=4e5+5;
int h[N],tot,n,q,fa[N][25],depth[N],w,x,y,z;
struct edge{int v,next;}a[N];
void add(int x,int y)
{
a[++tot]=(edge){y,h[x]};
h[x]=tot;
}
void getdepth(int x,int d,int father)
{
depth[x]=d,fa[x][1]=father;
for(int i=h[x];i;i=a[i].next)
{
int v=a[i].v;
if(v==father) continue;
getdepth(a[i].v,d+1,x);
}
}
int lca(int x,int y)
{
if(depth[x]<depth[y]) swap(x,y);
for(int i=20;i>=1;i--)
if(depth[fa[x][i]]>=depth[y])
x=fa[x][i];
if(x==y) return x;
for(int i=20;i>=1;i--)
if(fa[x][i]!=fa[y][i])
x=fa[x][i],y=fa[y][i];
return fa[x][1];
}
void check()
{
scanf("%d%d%d%d",&w,&x,&y,&z);
int p1=lca(w,x),p2=lca(y,z);
if(depth[p1]>=depth[p2])
{
if(lca(y,p1)==p1||lca(z,p1)==p1)
{
printf("Y\n");
return;
}
}
else
{
if(lca(w,p2)==p2||lca(x,p2)==p2)
{
printf("Y\n");
return;
}
}
printf("N\n");
}
int main()
{
scanf("%d%d",&n,&q);
int u,v;
for(int i=1;i<n;i++)
{
scanf("%d%d",&u,&v);
add(u,v),add(v,u);
}
getdepth(1,0,1);
for(int i=2;i<=20;i++)
for(int j=1;j<=n;j++)
fa[j][i]=fa[fa[j][i-1]][i-1];
for(int i=1;i<=q;i++)
check();
return 0;
}
|