把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【模板】LCA的RMQ做法(详)


在线算法

时间戳和欧拉序

时间戳(st[i]):第i个节点第一次被访问到的时间,即:若访问一个节点需要花费一个单位时间,第一次访问到当前节点是在第几个单位时间被访问到


欧拉序是一棵树按照dfs的顺序产生的序列,相当于模拟dfs的过程

For instance:

比如说,上面的例子的欧拉序就是:
1 1 1 2 2 2 4 4 4 2 2 2 5 5 5 8 8 8 5 5 5 2 2 2 1 1 1 3 3 3 6 6 6 3 3 3 7 7 7 3 3 3 1 1 1
而时间戳st[]:
s t [ 1 ] = 1 st[1]=1 st[1]=1
s t [ 2 ] = 2 st[2]=2 st[2]=2
s t [ 3 ] = 10 st[3]=10 st[3]=10
s t [ 4 ] = 3 st[4]=3 st[4]=3
s t [ 5 ] = 5 st[5]=5 st[5]=5
s t [ 6 ] = 11 st[6]=11 st[6]=11
s t [ 7 ] = 13 st[7]=13 st[7]=13
s t [ 8 ] = 6 st[8]=6 st[8]=6
(说白了时间戳就是欧拉序的下标 欧拉序记为seq[] 的话, s e q [ s t [ i ] ] = i seq[st[i]]=i seq[st[i]]=i

欧拉序的性质:(u,v)的lca一定在欧拉序的[st[u],st[v]]这个区间中
还是比较好理解的,因为访问每一个节点之前要先访问他的爸爸和祖先们,访问完了这个节点之后还要回溯回去再一次地访问他的爸爸和祖先们,所以[st[u],st[v]]内一定有u的祖先,还有v的祖先,所以lca就一定在这个区间之内

有了这个性质,但是我们还是不能准确地找出lca
经过仔细观察研究推敲思考对比得出:
敲黑板划重点:lca是[st[u],st[v]]中深度最小的那个点
注意是深度最小

可以这么理解
因为lca相当于这两个点互相联通的一个桥梁,假设在dfs的过程中,我们只有先通过了[st[u],st[v]]中深度最小的那个点才能到达另外那个点,而不过[st[u],st[v]]中深度最小的那个点,我们是到不了另外那个点的(dfs的性质)
(这里有点不好说,仔细体会一下)


由上,我们已经有了一个比较清晰的思路
在搞欧拉序的同时我们搞一个序列存与之对应的点的深度
然后用rmq求出深度最小的那个点,就是lca

还是这个例子
设e[]为欧拉序,dep[i]为节点e[i]的深度 st[i]为节点i在e[]中第一次出现的下标



在这里插入图片描述

rmq

RMQ可以用数据结构,比如线段树,也可以用st表

st表

这里主要说一下st表
复杂度 O ( n + n l o g n ) O(n+nlogn) O(n+nlogn),询问 O ( 1 ) O(1) O(1)
st表的思想其实也就是dp和倍增

f ( i , j ) f(i,j) f(i,j)表示 [ i , i + 2 j − 1 ] [i,i+2^j-1] [i,i+2j1]的最小值
[ i , i ] [i,i] [i,i]的最小值就是 a [ i ] a[i] a[i],所以 f ( i , 0 ) = a [ i ] f(i,0)=a[i] f(i,0)=a[i]
转移的时候将 [ i , i + 2 j − 1 ] [i,i+2^j-1] [i,i+2j1]平均分成两部分:
[ i , i + 2 j − 1 − 1 ] [i,i+2^{j-1}-1] [i,i+2j11] [ i + 2 j − 1 , i + 2 j − 1 ] [i+2^{j-1},i+2^j-1] [i+2j1,i+2j1]
[ i , i + 2 j − 1 ] [i,i+2^j-1] [i,i+2j1]的最小值是 [ i , i + 2 j − 1 − 1 ] [i,i+2^{j-1}-1] [i,i+2j11] [ i + 2 j − 1 , i + 2 j − 1 ] [i+2^{j-1},i+2^j-1] [i+2j1,i+2j1]中的最小值的最小值

f ( i , j ) = m i n ( f ( i , j − 1 ) , f ( i + 2 j − 1 , j − 1 ) ) f(i,j)=min(f(i,j-1),f(i+2^{j-1},j-1)) f(i,j)=min(f(i,j1),f(i+2j1,j1))

查询的时候 因为查询的区间 [ l , r ] [l,r] [l,r]的长度不一定就是2的整数次幂,所以我们就取区间长度的log值 (要下取整,不能上取整,否则会涉及到其他区间的数,可能会影响答案) 。由于不一定会覆盖到整个区间,我们把它劈成两部分,一部分以l为起点,另一部分以r为终点。由于是求最值,所以区间的重叠并没有什么影响。

另外,相邻两节点的深度的变化量为1,±1RMQ(约束RMQ)有更优的解法,这里先不展开

#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
#define MAXN 50005
int n,m;
struct node{
	int v,w;
};
vector<node>G[MAXN];//邻接表
int dp[MAXN*2][20];//st表
int dis[MAXN];//每个点到根节点的距离
int cnt=1,seq[MAXN*2-1]/*欧拉序*/,dep[MAXN*2-1],st[MAXN];

//dfs 得到dis seq dep st
void dfs(int u,int p,int d)
{
	seq[cnt]=u,st[u]=cnt,dep[cnt++]=d;
	for(int i=0;i<G[u].size();i++)
	{
		int v=G[u][i].v,w=G[u][i].w;
		if(v==p) continue;
		dis[v]=dis[u]+w;
		dfs(v,u,d+1);
		seq[cnt]=u;
		dep[cnt++]=d;
	}
}

//st表 
void RMQ(int k)
{
	for(int i=1;i<=k;i++)
		dp[i][0]=i;
	for(int j=1;(1<<j)/*2^j*/<=k;j++)
		for(int i=1;i+(1<<j)-1<=k;i++)
		{
			int a=dp[i][j-1],b=dp[i+(1<<(j-1))][j-1];
			if(dep[a]<dep[b])
				dp[i][j]=a;
			else dp[i][j]=b;
			//这里的st表存的是深度最低的点的seq的下标 
		}
}

//询问最小值 
int Query(int l,int r)
{
	int k=0;
	while(1<<(k+1)<=r-l+1)
		k++;
	int a=dp[l][k],b=dp[r-(1<<k)+1][k];
	if(dep[a]<dep[b])
		return a;
	return b;
}

int lca(int u,int v)
{
	return seq[Query(min(st[u],st[v]),max(st[u],st[v]))];
}
int main()
{
	
}


posted @ 2021-07-26 09:53  Starlight_Glimmer  阅读(20)  评论(0编辑  收藏  举报  来源
浏览器标题切换
浏览器标题切换end