题意:
给出一棵 N N N 个点的树, Q Q Q 组询问,每次询问给出四个点,组成树上的两条路径,求两条路径上的交点个数。 ( 5 ≤ N ≤ 1 0 5 , 1 ≤ Q ≤ 2 ∗ 1 0 4 ) (5\leq N\leq 10^5,1\leq Q\leq 2*10^4) (5≤N≤105,1≤Q≤2∗104)
思路:
比赛的时候看到这道题,想都没想就掏出链剖板子,区间加和区间和查询就过了。
赛后发现有些人是 LCA \text{LCA} LCA 做的,才发现原来 LCA \text{LCA} LCA 可以求路径交与路径并。
给出两条路径 ( a , b ) (a,b) (a,b) 与 ( c , d ) (c,d) (c,d),四个点两两求 LCA \text{LCA} LCA,得到 x 1 = l c a ( a , c ) , x 2 = l c a ( a , d ) , x 3 = l c a ( b , c ) , x 4 = l c a ( b , d ) x_1=lca(a,c),x_2=lca(a,d),x_3=lca(b,c),x_4=lca(b,d) x1=lca(a,c),x2=lca(a,d),x3=lca(b,c),x4=lca(b,d)。再从这四个点中取出深度最大的两个点 p 1 , p 2 p_1,p_2 p1,p2。
若 p 1 = ̸ p 2 p_1=\not p_2 p1≠p2,则两条路径一定有交,交点为 p 1 , p 2 p_1,p_2 p1,p2。若 p 1 = p 2 p_1=p_2 p1=p2,且 p 1 p_1 p1 的深度小于 l c a ( a , b ) lca(a,b) lca(a,b) 或者小于 l c a ( c , d ) lca(c,d) lca(c,d),则两条路径无交点,否则交点为 p 1 p_1 p1。
代码:
#include <bits/stdc++.h>
#define __ ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
#define rep(i,a,b) for(int i = a; i <= b; i++)
#define LOG1(x1,x2) cout << x1 << ": " << x2 << endl;
#define LOG2(x1,x2,y1,y2) cout << x1 << ": " << x2 << " , " << y1 << ": " << y2 << endl;
#define LOG3(x1,x2,y1,y2,z1,z2) cout << x1 << ": " << x2 << " , " << y1 << ": " << y2 << " , " << z1 << ": " << z2 << endl;
typedef long long ll;
typedef double db;
const int N = 1e5+100;
const db EPS = 1e-9;
using namespace std;
int n,q,t,tot,head[N],f[N][25],d[N];
struct Edge{
int to,next;
}e[2*N];
void add(int x,int y){
e[++tot].to = y, e[tot].next = head[x], head[x] = tot;
}
void dfs(int x,int fa){
f[x][0] = fa;
for(int i = 1; (1<<i) <= d[x]; i++)
f[x][i] = f[f[x][i-1]][i-1];
for(int i = head[x]; i; i = e[i].next){
int y = e[i].to;
if(y == fa) continue;
d[y] = d[x]+1; dfs(y,x);
}
}
int lca(int x,int y){
if(d[x] > d[y]) swap(x,y);
for(int i = t; i >= 0; i--)
if(d[f[y][i]] >= d[x]) y = f[y][i];
if(x == y) return x;
for(int i = t; i >= 0; i--)
if(f[x][i] != f[y][i]) x = f[x][i], y = f[y][i];
return f[x][0];
}
int main()
{
scanf("%d%d",&n,&q);
t = (int)(log(n)/log(2))+1;
tot = 1;
rep(i,1,n-1){
int a,b; scanf("%d%d",&a,&b);
add(a,b); add(b,a);
}
dfs(1,0);
rep(i,1,q){
int a,b,c,D; scanf("%d%d%d%d",&a,&b,&c,&D);
int x[5];
x[1] = lca(a,c), x[2] = lca(a,D), x[3] = lca(b,c), x[4] = lca(b,D);
int p1 = 0,p2 = 0;
rep(j,1,4)
if(d[x[j]] > d[p1]) p2 = p1, p1 = x[j];
else if(d[x[j]] > d[p2]) p2 = x[j];
int h1 = lca(a,b), h2 = lca(c,D);
if(p1 == p2){
if(d[p1] < d[h1] || d[p1] < d[h2]) printf("0\n");
else printf("1\n");
}
else{
int ans = d[p1]+d[p2]-2*d[lca(p1,p2)]+1;
printf("%d\n",ans);
}
}
return 0;
}
LCA \text{LCA} LCA例题扩展:
题意:
n
n
n 个点,
n
n
n 条边,给出图上两点,求图上两点最短距离,保证图连通。
(
1
≤
n
≤
1
0
5
)
(1\leq n\leq 10^5)
(1≤n≤105)
思路:
n
n
n 个点,图连通,则是在树上多加了一条边。因此对于图上两点来说,其最短距离要么经过多加的这条边,要么不经过这条边。
因此我们先用并查集处理出多加的这条边 ( u , v ) (u,v) (u,v),则对于 ( a , b ) (a,b) (a,b) 来说,其最短距离只有三种可能。一种是先在原树上求出 d i s ( a , b ) dis(a,b) dis(a,b),另一种是 d i s ( a , u ) + w ( u , v ) + d i s ( v , b ) dis(a,u)+w(u,v)+dis(v,b) dis(a,u)+w(u,v)+dis(v,b) 或 d i s ( a , v ) + w ( u , v ) + d i s ( u , b ) dis(a,v)+w(u,v)+dis(u,b) dis(a,v)+w(u,v)+dis(u,b)。三种情况取最小值即可完成此题。