【LCA模板】

描述

给定一棵 n 个点的树,Q个询问,每次询问点 x 到点 y两点之间的距离。

输入

第一行一个正整数 n ,表示这棵树有 n个节点;

接下来 n−1行,每行两个整数 x,y表示 x,y之间有一条连边;

然后一个整数 Q,表示有Q个询问;

接下来Q 行每行两个整数x,y 表示询问 x 到 y 的距离。

输出

输出 Q 行,每行表示每个询问的答案。

样例输入[复制]

6
1 2
1 3
2 4
2 5
3 6
2
2 6
5 6

样例输出[复制]

3
4

这里说三个方法:

①RMQ求LCA是O(n logn) 预处理然后O(1)求lca,空间也要带一个log。虽然查询O(1)很爽,但是如果n太大的话有可能预处理就炸了,也有可能空间不够。

主要流程是:

先dfs整个树,得到三个序列——欧拉序列【t】,深度序列【dep】 和 结点第一次出现的时间序列【pos】

【欧拉序列】:dfs到某个点时记一次,回溯到这个点时也记一次。那么显然长度就是2n【n为点数】

对这个欧拉序列做一个RMQ,记录区间深度最小值【dp数组存的是欧拉序列的下标,dep数组存的是欧拉序列对应点的深度】

然后查询u和v的lca时,就找到在dep[pos[u]]~dep[pos[v]]【如果pos[u]>pos[v]就把u和v交换一下】中深度最小的那个点就行了。

代码:

#include<bits/stdc++.h>
using namespace std;
const int maxn=101000;
const int maxm=202000;
int cnt,tot,N,u,v,Q,x,y;
int Head[maxn],Next[maxn],V[maxn];
int dist[maxn],dis[maxn],pos[maxn],vis[maxn];
int dp[maxm][20],dep[maxm],t[maxm];

void read(int &x){
	x=0;char ch=getchar();
	while(ch>'9'||ch<'0') ch=getchar();
	while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
}

void Add(int u,int v){
	++cnt;
	Next[cnt]=Head[u];
	V[cnt]=v;
	Head[u]=cnt;
}

void dfs(int u,int de){
	int i,v,w;
	if(!vis[u]) vis[u]=1,pos[u]=tot;
	dep[tot]=de,t[tot]=u,tot++;
	for(i=Head[u];i!=-1;i=Next[i]){
		v=V[i];
		if(vis[v]) continue;
		dfs(v,de+1);
		dep[tot]=de,t[tot]=u,tot++;
	}
}

void RMQ(){
	for(int j=0;(1<<j)<tot;j++){
		for(int i=0;i+(1<<j) <tot;i++){
			if(j==0) dp[i][j]=i;
			else{
				if(dep[dp[i][j-1]]<dep[dp[i+(1<<(j-1))][j-1]])
					dp[i][j]=dp[i][j-1];
				else
					dp[i][j]=dp[i+(1<<(j-1))][j-1];
			}
		}
	}
}
int Query(int p1,int p2){
	int k=log2(p2-p1+1);
	if(dep[dp[p1][k]]<dep[dp[p2-(1<<k)+1][k]])
		return t[dp[p1][k]];
	return t[dp[p2-(1<<k)+1][k]];
}

int lca(int v1,int v2){
	if(pos[v1]>pos[v2]) return Query(pos[v2],pos[v1]);
	return Query(pos[v1],pos[v2]);
}
void init(){
	cnt=tot=0;
	memset(Head,-1,sizeof(Head));
	memset(Next,-1,sizeof(Next));
}
int main(){
	init();
	read(N);
	for(int i=1;i<N;++i) read(u),read(v),Add(u,v);
	read(Q);
	dfs(1,0),RMQ();
	for(int op=1;op<=Q;++op){
		read(x),read(y);
		printf("%d\n",dep[pos[x]]+dep[pos[y]]-2*dep[pos[lca(x,y)]]);
	}
}

②倍增求LCA是O((n+q)logn),查询是logn的。写起来比较简单,但是常数比较大,可能会莫名其妙地挂掉。

大概就是先dfs一下求一下深度然后瞎搞。

#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+10;
int n,m,u,v,cnt=0;
int Next[maxn<<1],V[maxn<<1],Head[maxn],dep[maxn],f[maxn][20];
void read(int &x){
    x=0;char ch=getchar();
    while(ch>'9'||ch<'0') ch=getchar();
    while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
}
void add(int u,int v){
    ++cnt;
    Next[cnt]=Head[u];
    V[cnt]=v;
    Head[u]=cnt;
}
void dfs(int x,int fa){
    f[x][0]=fa;
    for(int i=1;i<19;++i)
        f[x][i]=f[f[x][i-1]][i-1];
    dep[x]=dep[fa]+1;
    for(int i=Head[x];i;i=Next[i])
        if(V[i]!=fa)
            dfs(V[i],x);
}
int lca(int x,int y){
    if(dep[x]<dep[y]) swap(x,y);
    for(int i=18;i>=0;i--)
        if(dep[f[x][i]]>=dep[y])
            x=f[x][i];
    if(x==y) return x;
    for(int i=18;i>=0;i--)
        if(f[x][i]!=f[y][i])
            x=f[x][i],y=f[y][i];
    return f[x][0];
}
int main(){
    read(n);
    for(int i=1;i<n;++i){
        read(u),read(v);
        add(u,v),add(v,u);
    }
    dfs(1,0),read(m);
    for(int i=1;i<=m;++i){
        read(u),read(v);
        int g=lca(u,v);
        printf("%d",dep[u]+dep[v]-2*dep[g]);
        putchar('\n');
    }
}

③树链剖分先dfs两次,也就是O(n)预处理,查询也是logn【可以证明重链只有logn条】的,比倍增要快一些。而且空间是O(n)的,比倍增空间复杂度低。求lca的时候就是:只要x和y不在同一条重链上,就把更深的点往上跳。

代码:

#include<bits/stdc++.h>
using namespace std;
const int maxn=100010;
int Head[maxn],Next[maxn<<1],V[maxn<<1];
int depth[maxn],son[maxn],fa[maxn],siz[maxn],top[maxn];
int n,q,x,y,cnt=0;
void add(int u,int v){
	++cnt;
	Next[cnt]=Head[u];
	V[cnt]=v;
	Head[u]=cnt;
}
void addedge(int u,int v){add(u,v),add(v,u);}
void dfs1(int u,int f){
	siz[u]=1,son[u]=0,fa[u]=f;
	for(int i=Head[u];i;i=Next[i]){
		int v=V[i];
		if(v==f) continue;
		depth[v]=depth[u]+1;
		dfs1(v,u);
		siz[u]+=siz[v];
		if(siz[son[u]]<siz[v])
			son[u]=v;
	}
}
void dfs2(int u,int fa){
	top[u]=(u==son[fa])?top[fa]:u;
	for(int i=Head[u];i;i=Next[i]){
		int v=V[i];
		if(v==fa) continue;
		dfs2(v,u);
	}
}

int lca(int x,int y){
	for(;top[x]!=top[y];depth[top[x]]>depth[top[y]]?x=fa[top[x]]:y=fa[top[y]]);
	return depth[x]<depth[y]?x:y;
}

int main(){
	scanf("%d",&n);
	for(int i=1;i<n;++i)
		scanf("%d%d",&x,&y),addedge(x,y);
	dfs1(1,0),dfs2(1,0);
	scanf("%d",&q);
	while(q--){
		scanf("%d%d",&x,&y);
		printf("%d\n",depth[x]+depth[y]-2*depth[lca(x,y)]);
	}
}

猜你喜欢

转载自blog.csdn.net/g21wcr/article/details/83017865
今日推荐