求LCA最近公共祖先的在线ST算法_C++
2017-02-23 21:32
330 查看
ST算法是求最近公共祖先的一种 在线 算法,基于RMQ算法,本代码用双链树存树
预处理的时间复杂度是 O(nlog2n) 查询时间是 O(1) 的
另附上离线算法 Tarjan 的链接:
首先预处理出深度,以及 DFS 序,这里的DFS序是指回溯时也算上,比如:
然后记录每个节点在 DFS 序中第一次出现的位置,b[i] 为第 i 号节点第一次出现的位置
开始 DP 处理区间区间内最小值,这里使用 RMQ 算法,其功能类似于线段树或树状数组
f[i][j] 表示从第 i 位开始,连续 2j 个数的最小值,状态转移:
因为它是 2 的幂次方的状态,所以每次转移可以看做把当前状态分为两个相等的部分,求两部分的最小值
如: 5 7 3 2 和 4 6 1 5
min=2 min=1
即 f[1][2]=2 f[5][2]=1
所以 f[1][3]=min(f[1][2],f[5][2])=1
初始状态:f[i][0]=d[a[i]] loc[i][0]=a[i]
注意这里 f 记录的是它的深度的最小值,而位置用 loc 记录
代码用变量优化了一下常数
接着开始进行询问
读入两个节点,查询它们第一次出现的位置
在这两个位置之间的区间查询最小深度的节点,该节点即为最近公共祖先
查询区间时,我们把它分成两个部分,可以有重叠,如
8 9 6 5 6 8 4
这7个节点,把它分成: 8 9 6 5 和 5 6 8 4
min=5 min=4
则最小值为 min(5,4)=4
可以这样理解:
将两个位置的距离取个对数记为 i,然后从最左边,往后共 2i 个数的最小值,这是第一部分
第二个部分是从右边往左推 2i 个数,即 y-2i+1,然后再往后取 2i 个数
成功将区间分为两部分
代码内有常数优化,有的地方思路可能不是很清晰,请谅解
给个完整代码
预处理的时间复杂度是 O(nlog2n) 查询时间是 O(1) 的
另附上离线算法 Tarjan 的链接:
首先预处理出深度,以及 DFS 序,这里的DFS序是指回溯时也算上,比如:
void dfs(int x,int dep) { int i; d[x]=dep; a[++top]=x; for (i=down[x];i!=0;i=next[i]) { dfs(i,dep+1); a[++top]=x; } }
然后记录每个节点在 DFS 序中第一次出现的位置,b[i] 为第 i 号节点第一次出现的位置
for (i=1;i<=top;i++) if (b[a[i]]==0) b[a[i]]=i;
开始 DP 处理区间区间内最小值,这里使用 RMQ 算法,其功能类似于线段树或树状数组
f[i][j] 表示从第 i 位开始,连续 2j 个数的最小值,状态转移:
f[i][j]=min(f[i][j-1],f[i+(1<<(j-1))][j-1])
因为它是 2 的幂次方的状态,所以每次转移可以看做把当前状态分为两个相等的部分,求两部分的最小值
如: 5 7 3 2 和 4 6 1 5
min=2 min=1
即 f[1][2]=2 f[5][2]=1
所以 f[1][3]=min(f[1][2],f[5][2])=1
初始状态:f[i][0]=d[a[i]] loc[i][0]=a[i]
注意这里 f 记录的是它的深度的最小值,而位置用 loc 记录
void init() { int i,j,s,x,k; for (i=1;i<=top;i++) { f[i][0]=d[a[i]]; loc[i][0]=a[i]; } s=log2(top); for (j=1;j<=s;j++) { k=top-(1<<j)+1; for (i=1;i<=k;i++) { x=i+(1<<(j-1)); if (f[i][j-1]<=f[x][j-1]) { f[i][j]=f[i][j-1]; loc[i][j]=loc[i][j-1]; } else { f[i][j]=f[x][j-1]; loc[i][j]=loc[x][j-1]; } } } }
代码用变量优化了一下常数
接着开始进行询问
读入两个节点,查询它们第一次出现的位置
在这两个位置之间的区间查询最小深度的节点,该节点即为最近公共祖先
查询区间时,我们把它分成两个部分,可以有重叠,如
8 9 6 5 6 8 4
这7个节点,把它分成: 8 9 6 5 和 5 6 8 4
min=5 min=4
则最小值为 min(5,4)=4
min(f[x][log2(y-x)],f[y-(1<<i)+1][log2(y-x)]);
可以这样理解:
将两个位置的距离取个对数记为 i,然后从最左边,往后共 2i 个数的最小值,这是第一部分
第二个部分是从右边往左推 2i 个数,即 y-2i+1,然后再往后取 2i 个数
成功将区间分为两部分
scanf("%d",&t); while (t>0) { t--; scanf("%d%d",&x,&y); x=b[x]; y=b[y]; if (x>y) swap(x,y); i=log2(y-x); k=y-(1<<i)+1; printf("%d\n",f[x][i]<f[k][i]?loc[x][i]:loc[k][i]); }
代码内有常数优化,有的地方思路可能不是很清晰,请谅解
给个完整代码
#include<cstdio> #include<cstdlib> #include<cstring> #include<cmath> #include<iostream> #include<algorithm> #define N 100001 using namespace std; int a[N*2],d ,down ,next ,top,f[2*N][18],loc[2*N][18],n,b ; int log2(int x) { int k=0; while (x>1) { x/=2; k++; } return k; } void dfs(int x,int dep) { int i; d[x]=dep; a[++top]=x; for (i=down[x];i!=0;i=next[i]) { dfs(i,dep+1); a[++top]=x; } } void init() { int i,j,s,x,k; for (i=1;i<=top;i++) { f[i][0]=d[a[i]]; loc[i][0]=a[i]; } s=log2(top); for (j=1;j<=s;j++) { k=top-(1<<j)+1; for (i=1;i<=k;i++) { x=i+(1<<(j-1)); if (f[i][j-1]<=f[x][j-1]) { f[i][j]=f[i][j-1]; loc[i][j]=loc[i][j-1]; } else { f[i][j]=f[x][j-1]; loc[i][j]=loc[x][j-1]; } } } } int main() { int i,k,x,y,t; scanf("%d",&n); for (i=1;i<=n;i++) down[i]=d[i]=next[i]=0; for (i=1;i<=n;i++) { scanf("%d",&x); next[i]=down[x]; down[x]=i; } top=0; dfs(down[0],1); for (i=1;i<=top;i++) if (b[a[i]]==0) b[a[i]]=i; init(); scanf("%d",&t); while (t>0) { t--; scanf("%d%d",&x,&y); x=b[x]; y=b[y]; if (x>y) swap(x,y); i=log2(y-x); k=y-(1<<i)+1; printf("%d\n",f[x][i]<f[k][i]?loc[x][i]:loc[k][i]); } return 0; }
相关文章推荐
- 求LCA最近公共祖先的在线ST算法_C++
- LCA最近公共祖先的离线算法(Tarjan)和在线算法(ST)
- 求LCA最近公共祖先的在线倍增算法模板_C++
- LCA(最近公共祖先算法)之在线st表法
- 最近公共祖先(LCA)算法实现过程 【Tarjan离线+倍增在线+RMQ】
- c++最近公共祖先LCA(倍增算法和tarjan)
- 倍增法求最近公共祖先(LCA)的算法模板
- LCA 最近公共祖先——Tarjan(离线)算法的基本思路及其算法实现
- 最近公共祖先LCA(Tarjan与DFS--ST倍增)
- 谈谈最近公共祖先(LCA)——杨子曰算法
- 算法模板之最近公共祖先问题(LCA)
- 求LCA最近公共祖先的离线Tarjan算法_C++
- POJ1986 Distance Queries【最近公共祖先】【Tarjan-LCA算法】
- LCA (最近公共祖先)倍增做法 —— O(nlogn)预处理 O(logn)(在线)查询
- LCA(最近公共祖先)算法的理解。
- LCA 最近公共祖先-Tarjan(离线)算法的基本思路及其算法实现
- 寻找最近公共祖先算法(LCA)
- 最近公共祖先LCA(Tarjan算法)的思考和算法实现
- 最近公共祖先 朴素 离线 在线 算法合集
- 【算法】最近公共祖先之在线算法(RMQ-ST)