LCA--ST表+欧拉序

预备知识:

存图的几种方式 链接

欧拉序与dfs序区别

一.ST表+欧拉序

①原理:

欧拉序前序遍历得到的序列,叫dfs序,但数字可以重复出现,一进一出,叫欧拉序),会发现根结点总在中间,而根结点是该段序列深度最小的点

因此两个点的LCA,就是在该序列上两个点第一次出现的区间内深度最小的那个点

即转化为区间RMQ问题,可以用ST表。当然你可以再写一棵线段树(如果有修改操作)

②实现细节:

主要分为求欧拉序和RMQ两部分。

a.欧拉序:图的遍历(几种存储结构写法不太一样)

cnt:序列长度(每个元素一进一出共两次,记得最大初始化为2*MAXN

oula[]:欧拉序列,记录编号   dfs前记录一次,dfs后(回溯)再记录一次

depth[]:每个编号的深度(也可以记录每个下标的深度,见注释)

start[]:每个编号第一次出现的序列下标

b.ST表  不懂ST表的,看链接

minl[i][j]    记得第一层初始化为depth[]

pos[][]最值下标,第一层初始化为i

注意这里i是欧拉序列的下标,最终要的是编号这里经常下标搞混!!

欧拉序列下标=start【编号】

③代码

a.例题:P3379   被卡常难受,最后还是A了

我用了vector,参考代码链接

#pragma GCC optimize(3)
#include <bits/stdc++.h>
using namespace std;
#define N 500010  
vector<int>G[N]; 
int oula[N<<1],depth[N],start[N];//N*2
int cnt;

int minl[N<<1][19],pos[N<<1][19],len[N<<1],vis[N],tmp; //开20以上就TLE,自闭
//struct node{int deep,order;}minl[N][19];

int n,m,s,x,y;

inline int read()
{
    char ch='*';
    while(!isdigit(ch=getchar()));
    int num=ch-'0';
    while(isdigit(ch=getchar()))num=num*10+ch-'0';
    return num;
}

inline void dfs(int now,int fa,int deep){
    oula[++cnt]=now;//入 
    //depth[cnt]=deep;
    if(depth[now]==0)depth[now]=deep;
    if(start[now]==0)start[now]=cnt;
    int z=G[now].size();
    for(int i=0;i<z;i++){
        if(G[now][i]!=fa){
            dfs(G[now][i],now,deep+1);
            oula[++cnt]=now;//出 
            //depth[cnt]=deep;
            //index[now]=cnt;
        }
    }
}

inline void S_table(){
    for(int i=1;i<2*N;++i) len[i]=(1<<(tmp+1))==i?++tmp:tmp;
    for(int i=1;i<=cnt;i++) minl[i][0]= depth[oula[i]],pos[i][0]=i;//depth[i]
    //int l = log2((double)cnt);
    for (int j=1;(1<<j)<=cnt;j++){
        for (int i=1; i + (1 << (j-1) ) - 1 <=cnt;++i){
            //minl[i][j] = min(minl[i][j-1], minl[i + (1 << (j-1) )][j-1]);
          if(minl[i][j-1]<minl[i+(1<<(j-1))][j-1])minl[i][j]=minl[i][j-1],pos[i][j]=pos[i][j-1];
            else minl[i][j]=minl[i + (1 << (j-1) )][j-1],pos[i][j]=pos[i + (1 << (j-1) )][j-1];
        }
    }
}

inline int rmq(int l, int r){
    
    if(l>r) swap(l,r);
    int k=len[r-l+1];
    //int k = log2((double)(r-l+1));
    int mid=r-(1<<k)+1;

    if(minl[l][k]<=minl[mid][k])return pos[l][k];
    else return pos[mid][k];
}
int main()
{
    n=read();m=read();s=read(); 
    for(int i=1;i<n;++i)
    {
      x=read();y=read();
      G[x].push_back(y);
      G[y].push_back(x);
    }

    dfs(s,-1,1);//求欧拉序列
    S_table();//初始化st表
    
    for(int i=1;i<=m;++i)
    {
        x=read();y=read();
        printf("%d\n",oula[rmq(start[x],start[y])]);
    }
    return 0;
}

 

b.POJ1310  Nearest Common Ancestors

裸题,只不过多组输入,注意初始化,还有先找根结点

#include<cmath>
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define N 100010  
vector<int>G[N]; 
int flag[N];
int oula[N<<1],depth[N],start[N];
int T,cnt;

int minl[N<<1][19],pos[N<<1][19],len[N<<1],vis[N],tmp; 
//struct node{int deep,order;}minl[N][30];

int n,m,s,x,y;

inline int read()
{
    int x=0;
    char c=getchar();
    bool flag=0;
    while(c<'0'||c>'9'){if(c=='-')flag=1;    c=getchar();}
    while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+c-'0';c=getchar();}
    return flag?-x:x;
}

inline void dfs(int now,int fa,int deep){
	oula[++cnt]=now;//入 
	//if(!vis[now])vis[now]=1,
	//depth[cnt]=deep;
	if(depth[now]==-1)depth[now]=deep;
	if(start[now]==-1)start[now]=cnt;
	for(int i=0;i<G[now].size();i++){
		if(G[now][i]!=fa){
			dfs(G[now][i],now,deep+1);
			oula[++cnt]=now;//出 
			//depth[cnt]=deep;
			//index[now]=cnt;
		}
	}
}

inline void S_table(){
	//for(int i=1;i<2*N;++i) len[i]=(1<<(tmp+1))==i?++tmp:tmp;
	for(int i=1;i<=cnt;i++) minl[i][0]= depth[oula[i]],pos[i][0]=i;//depth[i]
    int l = log2((double)cnt);
    for (int j=1;j<=l;j++){
        for (int i=1; i + (1 << (j-1) ) - 1 <=cnt;++i){
            //minl[i][j] = min(minl[i][j-1], minl[i + (1 << (j-1) )][j-1]);
            if(minl[i][j-1]<minl[i+(1<<(j-1))][j-1])minl[i][j]=minl[i][j-1],pos[i][j]=pos[i][j-1];
            else minl[i][j]=minl[i + (1 << (j-1) )][j-1],pos[i][j]=pos[i + (1 << (j-1) )][j-1];
        }
    }
}

inline int rmq(int l, int r){
	
	if(l>r) swap(l,r);
	int k=len[r-l+1];
    //int k = log2((double)(r-l+1));
    int mid=r-(1<<k)+1;

    if(minl[l][k]<=minl[mid][k])return pos[l][k];
    else return pos[mid][k];
}
int main()
{
	scanf("%d",&T);
	for(int i=1;i<2*N;++i) len[i]=(1<<(tmp+1))==i?++tmp:tmp;//这个单独初始化
    while(T--){
    	n=read();
    memset(flag,0,sizeof(flag));
    memset(depth,-1,sizeof(depth));
    memset(start,-1,sizeof(start));
    memset(oula,0,sizeof(oula)) ;
    for(int i=1;i<=n;i++)G[i].clear();
    for(int i=1;i<n;++i)
    {
      x=read();y=read();
      flag[y]=1;
      cnt=0;
      G[x].push_back(y);
      G[y].push_back(x);
    }
    
        for(int i=1;i<=n;i++) //找根结点
		if(!flag[i])
        {
            s = i;
            break;
        }

    dfs(s,-1,1);
    S_table();
    
    //for(int i=1;i<=m;++i)
    {
        x=read();y=read();
        printf("%d\n",oula[rmq(start[x],start[y])]);
    }
	}
    
    return 0;
}

c.HDU 2586 How far away? 两点距离1 带权值

两点间距离=dis[x]+dis[y]-2*dis[lca]

学习树上前缀和(边权、点权)

#include <bits/stdc++.h>
using namespace std;
#define N 40010  //开大会MLE
struct node{
	int t;
	int w;
};
vector<node>G[N]; 
int flag[N];
int oula[N<<1],depth[N],start[N],dis[N];
int T,cnt;

int minl[N<<1][19],pos[N<<1][19],len[N<<1],vis[N],tmp; 


int n,m,s,x,y,z;

inline int read()
{
    int x=0;
    char c=getchar();
    bool flag=0;
    while(c<'0'||c>'9'){if(c=='-')flag=1;    c=getchar();}
    while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+c-'0';c=getchar();}
    return flag?-x:x;
}

inline void dfs(int now,int fa,int deep){
	oula[++cnt]=now;//入 
	if(depth[now]==-1)depth[now]=deep;
	if(start[now]==-1)start[now]=cnt;
	for(int i=0;i<G[now].size();i++){
		if(G[now][i].t!=fa){
			//dis[G[now][i].t]=dis[now]+G[now][i].w;
			dfs(G[now][i].t,now,deep+G[now][i].w);
			oula[++cnt]=now;//出 

		}
	}
}

inline void S_table(){
	//for(int i=1;i<2*N;++i) len[i]=(1<<(tmp+1))==i?++tmp:tmp;
	for(int i=1;i<=cnt;i++) minl[i][0]= depth[oula[i]],pos[i][0]=i;//depth[i]
    int l = log2((double)cnt);
    for (int j=1;j<=l;j++){
        for (int i=1; i + (1 << (j-1) ) - 1 <=cnt;++i){
            //minl[i][j] = min(minl[i][j-1], minl[i + (1 << (j-1) )][j-1]);
            if(minl[i][j-1]<minl[i+(1<<(j-1))][j-1])minl[i][j]=minl[i][j-1],pos[i][j]=pos[i][j-1];
            else minl[i][j]=minl[i + (1 << (j-1) )][j-1],pos[i][j]=pos[i + (1 << (j-1) )][j-1];
        }
    }
}

inline int rmq(int l, int r){
	
	if(l>r) swap(l,r);
	int k=len[r-l+1];
    int mid=r-(1<<k)+1;
    return min(minl[l][k],minl[mid][k]);
    //if(minl[l][k]<=minl[mid][k])return pos[l][k];
    //else return pos[mid][k];
}
int main()
{
	scanf("%d",&T);
	for(int i=1;i<2*N;++i) len[i]=(1<<(tmp+1))==i?++tmp:tmp;
    while(T--){
    	n=read();
    	m=read();
    memset(flag,0,sizeof(flag));
    memset(depth,-1,sizeof(depth));
    memset(start,-1,sizeof(start));
    memset(oula,0,sizeof(oula)) ;
    for(int i=1;i<=n;i++)G[i].clear();
    for(int i=1;i<n;++i)
    {
      x=read();y=read();z=read();
      flag[y]=1;
      cnt=0;
      node tmp1;
      tmp1.t=y;
      tmp1.w=z;
      G[x].push_back(tmp1);
      tmp1.t=x;
      tmp1.w=z;
      G[y].push_back(tmp1);
    }
    
        for(int i=1;i<=n;i++) 
		if(!flag[i])
        {
            s = i;
            break;
        }
        //cout<<s<<endl;
    dfs(s,-1,0);//这里初始deep当dis用,设为0
    S_table();
    
    for(int i=1;i<=m;++i)
    {
        x=read();y=read();
        printf("%d\n",depth[x]+depth[y]-2*rmq(start[x],start[y]));
        //printf("%d\n",dis[x]+dis[y]-2*dis[rmq(start[x],start[y])]);

    }
	}
    
    return 0;
}

 

d.  POJ 1986  Distance Queries   两点距离2根任意

#include <iostream>
#include<string.h>
#include<time.h>
#include<map>
#include<stdio.h>
#include<vector>
#include<cmath>
using namespace std;
#define N 80010  
struct node{
	int t;
	int w;
};
vector<node>G[N]; 
int flag[N];
int oula[N<<1],depth[N],start[N],dis[N];
int T,cnt;

int minl[N<<1][19],pos[N<<1][19],len[N<<1],tmp; 

int n,k,m,s,x,y,z;
char o;

inline int read()
{
    int x=0;
    char c=getchar();
    bool flag=0;
    while(c<'0'||c>'9'){if(c=='-')flag=1;    c=getchar();}
    while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+c-'0';c=getchar();}
    return flag?-x:x;
}

inline void dfs(int now,int fa,int deep){
	oula[++cnt]=now;//入 
	if(depth[now]==-1)depth[now]=deep;
	if(start[now]==-1)start[now]=cnt;
	for(int i=0;i<G[now].size();i++){
		if(G[now][i].t!=fa){
			dfs(G[now][i].t,now,deep+G[now][i].w);
			oula[++cnt]=now;//出 

		}
	}
}

inline void S_table(){
	for(int i=1;i<=cnt;i++) minl[i][0]= depth[oula[i]],pos[i][0]=i;//depth[i]
    int l = log2((double)cnt);
    for (int j=1;j<=l;j++){
        for (int i=1; i + (1 << (j-1) ) - 1 <=cnt;++i){
        
            if(minl[i][j-1]<minl[i+(1<<(j-1))][j-1])minl[i][j]=minl[i][j-1],pos[i][j]=pos[i][j-1];
            else minl[i][j]=minl[i + (1 << (j-1) )][j-1],pos[i][j]=pos[i + (1 << (j-1) )][j-1];
        }
    }
}

inline int rmq(int l, int r){
	
	if(l>r) swap(l,r);
	int k=len[r-l+1];
    //int k = log2((double)(r-l+1));
    int mid=r-(1<<k)+1;
    return min(minl[l][k],minl[mid][k]);

}
int main()
{
	//scanf("%d",&T);
	for(int i=1;i<2*N;++i) len[i]=(1<<(tmp+1))==i?++tmp:tmp;
    while(~scanf("%d%d",&n,&k)){
    	cnt=0;
    memset(flag,0,sizeof(flag));
    memset(depth,-1,sizeof(depth));
    memset(start,-1,sizeof(start));
    memset(oula,0,sizeof(oula)) ;
    memset(ans,0,sizeof(ans));
    for(int i=1;i<=n;i++)G[i].clear();
    for(int i=1;i<=k;++i)
    {
    	scanf("%d%d%d %c",&x,&y,&z,&o);
//      	flag[y]=1;
      node tmp1;
      tmp1.t=y;
      tmp1.w=z;
      G[x].push_back(tmp1);
      tmp1.t=x;
      G[y].push_back(tmp1);
      
    }
    
//        for(int i=1;i<=n;i++) 
//		if(!flag[i])
//        {
//            s = i;
//            break;
//        }
        //cout<<s<<endl;
        s=1;
    dfs(s,-1,0);//这里初始deep=0
    S_table();
    m=read();
    for(int i=1;i<=m;++i)
    {
        scanf("%d%d",&x,&y);
        printf("%d\n",depth[x]+depth[y]-2*rmq(start[x],start[y]));
        //printf("%d\n",oula[rmq(start[x],start[y])]);
    }

	}
    
    return 0;
}

 

e.POJ 1470  Closest Common Ancestors 输入格式

记录结点是lca的次数,但是输入消除空格有trick,还有是多组输入!!!答案数组(也可以用map存)每次要初始化(忘了导致wa)

#include <iostream>
#include<string.h>
#include<time.h>
#include<map>
#include<stdio.h>
#include<vector>
#include<cmath>
using namespace std;
#define N 1010  

vector<int>G[N]; 
map<int,int>ans;
int flag[N];
int oula[N<<1],depth[N],start[N],dis[N];
int T,cnt;

int minl[N<<1][19],pos[N<<1][19],len[N<<1],tmp; 


int n,m,s,x,y,z;

inline int read()
{
    int x=0;
    char c=getchar();
    bool flag=0;
    while(c<'0'||c>'9'){if(c=='-')flag=1;    c=getchar();}
    while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+c-'0';c=getchar();}
    return flag?-x:x;
}

inline void dfs(int now,int fa,int deep){
	oula[++cnt]=now;//入 
	if(depth[now]==-1)depth[now]=deep;
	if(start[now]==-1)start[now]=cnt;
	for(int i=0;i<G[now].size();i++){
		if(G[now][i]!=fa){
			dfs(G[now][i],now,deep+1);
			oula[++cnt]=now;//出 

		}
	}
}

inline void S_table(){
	//for(int i=1;i<2*N;++i) len[i]=(1<<(tmp+1))==i?++tmp:tmp;
	for(int i=1;i<=cnt;i++) minl[i][0]= depth[oula[i]],pos[i][0]=i;//depth[i]
    int l = log2((double)cnt);
    for (int j=1;j<=l;j++){
        for (int i=1; i + (1 << (j-1) ) - 1 <=cnt;++i){
        
            if(minl[i][j-1]<minl[i+(1<<(j-1))][j-1])minl[i][j]=minl[i][j-1],pos[i][j]=pos[i][j-1];
            else minl[i][j]=minl[i + (1 << (j-1) )][j-1],pos[i][j]=pos[i + (1 << (j-1) )][j-1];
        }
    }
}

inline int rmq(int l, int r){
	
	if(l>r) swap(l,r);
	int k=len[r-l+1];
    //int k = log2((double)(r-l+1));
    int mid=r-(1<<k)+1;

    if(minl[l][k]<=minl[mid][k])return pos[l][k];
    else return pos[mid][k];
}
int main()
{

	for(int i=1;i<2*N;++i) len[i]=(1<<(tmp+1))==i?++tmp:tmp;
    while(~scanf("%d",&n)){
    	cnt=0;
    memset(flag,0,sizeof(flag));
    memset(depth,-1,sizeof(depth));
    memset(start,-1,sizeof(start));
    memset(oula,0,sizeof(oula)) ;
    ans.clear();//清空
    for(int i=1;i<=n;i++)G[i].clear();
    for(int i=1;i<=n;++i)
    {
    	scanf("%d:(%d)",&x,&z);
      while(z--){
      	scanf("%d",&y);
      	flag[y]=1;
      G[x].push_back(y);
      G[y].push_back(x);
	  }
      
    }
    
        for(int i=1;i<=n;i++) 
		if(!flag[i])
        {
            s = i;
            break;
        }
        //cout<<s<<endl;
    dfs(s,-1,1);//这里初始deep=1
    S_table();
    scanf("%d",&m);
    for(int i=1;i<=m;++i)
    {
    	while(getchar() != '(');//吸收括号 
        scanf("%d%d",&x,&y);
        while(getchar() != ')');
        ans[oula[rmq(start[x],start[y])]]++;
        //printf("%d\n",depth[x]+depth[y]-2*rmq(start[x],start[y]));
        //printf("%d\n",oula[rmq(start[x],start[y])]);
    }
     map<int,int>::iterator     it=ans.begin();   
    for(;it!=ans.end();it++){
    	printf("%d:%d\n",it->first,it->second);
	}

	}
    
    return 0;
}

f.ZOJ 3195三点距离(样例间的换行)

两两距离的和/2

#include <bits/stdc++.h>
using namespace std;
#define N 50050  
struct node{
	int t;
	int w;
};
vector<node>G[N]; 
int flag[N];
int oula[N<<1],depth[N],start[N],dis[N];
int T,cnt;

int minl[N<<1][20],pos[N<<1][20],len[N<<1],vis[N],tmp; 


int n,m,s,x,y,z;

inline int read()
{
    int x=0;
    char c=getchar();
    bool flag=0;
    while(c<'0'||c>'9'){if(c=='-')flag=1;    c=getchar();}
    while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+c-'0';c=getchar();}
    return flag?-x:x;
}

inline void dfs(int now,int fa,int deep){
	oula[++cnt]=now;//入 
	if(depth[now]==-1)depth[now]=deep;
	if(start[now]==-1)start[now]=cnt;
	for(int i=0;i<G[now].size();i++){
		if(G[now][i].t!=fa){
			//dis[G[now][i].t]=dis[now]+G[now][i].w;
			dfs(G[now][i].t,now,deep+G[now][i].w);
			oula[++cnt]=now;//出 

		}
	}
}

inline void S_table(){
	//for(int i=1;i<2*N;++i) len[i]=(1<<(tmp+1))==i?++tmp:tmp;
	for(int i=1;i<=cnt;i++) minl[i][0]= depth[oula[i]],pos[i][0]=i;//depth[i]
    int l = log2((double)cnt);
    for (int j=1;j<=l;j++){
        for (int i=1; i + (1 << (j-1) ) - 1 <=cnt;++i){
            if(minl[i][j-1]<minl[i+(1<<(j-1))][j-1])minl[i][j]=minl[i][j-1],pos[i][j]=pos[i][j-1];
            else minl[i][j]=minl[i + (1 << (j-1) )][j-1],pos[i][j]=pos[i + (1 << (j-1) )][j-1];
        }
    }
}

inline int rmq(int l, int r){
	
	if(l>r) swap(l,r);
	int k=len[r-l+1];
    //int k = log2((double)(r-l+1));
    int mid=r-(1<<k)+1;
    return min(minl[l][k],minl[mid][k]);
    //if(minl[l][k]<=minl[mid][k])return pos[l][k];
    //else return pos[mid][k];
}
int main()
{
	for(int i=1;i<2*N;++i) len[i]=(1<<(tmp+1))==i?++tmp:tmp;
    int cas=0;
    while(~scanf("%d",&n)){
   if(cas++)printf("\n");
    cnt=0;
    memset(flag,0,sizeof(flag));
    memset(depth,-1,sizeof(depth));
    memset(start,-1,sizeof(start));
    memset(oula,0,sizeof(oula)) ;
    for(int i=0;i<=n-1;i++)G[i].clear();//下标①
    for(int i=1;i<n;++i)
    {
      x=read();y=read();z=read();
      flag[y]=1;
      node tmp1;
      tmp1.t=y;
      tmp1.w=z;
      G[x].push_back(tmp1);
      tmp1.t=x;
      tmp1.w=z;
      G[y].push_back(tmp1);
    }
    
        for(int i=0;i<=n-1;i++) //下标②
		if(!flag[i])
        {
            s = i;
            break;
        }
        //cout<<s<<endl;
    dfs(s,-1,0);//这里初始deep当dis用,设为0
    S_table();
    m=read();
    for(int i=1;i<=m;++i)
    {
        x=read();y=read();z=read();
        printf("%d\n",depth[x]+depth[y]+depth[z]-rmq(start[x],start[y])-rmq(start[y],start[z])-rmq(start[x],start[z]));//三点距离
        
    }
   
	}
    
    return 0;
}

二.倍增法 

理解链接  代码链接(qq牛逼)

①先跑深度大的  ②深度相同一起跑,但不同于暴力,每次往上跑2^j次方,注意跑到相同,不一定是最近的祖先

三. Tarjan

理解链接

Tarjan其他应用理解

Tarjan核心思想先dfs到叶子→退栈(回溯)后才更新

lca与求强连通写法略有差距:

因为lca侧重记录根结点,需用并查集(有点类似下面说到的low数组),而且需要离线处理询问,dfs的时候“顺便”把询问完成(不然到最后,并查集求得大家祖先都是1,就没意义了)

强连通分量侧重记录具体有谁,要用栈维护,还有low和dfn数组,判断是不是同一个连通分量。

四.树链剖分

待更

猜你喜欢

转载自blog.csdn.net/zjyang12345/article/details/89683763