最近公共祖先

最近公共祖先

方法:

  1. 向上标记法 O ( n ) O(n) O(n)
  2. 倍增
  • f a [ i , j ] fa[i,j] fa[i,j]表示从i开始,向上走 2 j 2^j 2j步所能走到的节点。 0 < = j < = l o g ( n ) 0<=j<=log(n) 0<=j<=log(n), d e p t h [ i ] depth[i] depth[i]表示深度
    • [1]先将两个点跳到同一层
    • [2]让两个点同时往上跳,一直跳到它们的最近公共祖先的下一层。
  • 预处理时间复杂度: O ( n l o g ( n ) ) O(nlog(n)) O(nlog(n))
  • 查询复杂度: O ( l o g ( n ) ) O(log(n)) O(log(n))
  1. tarjan------离线求LCA( O ( n + m ) O(n+m) O(n+m))

马上要caip省赛,应该还来得及

题单

1.祖孙询问

公共祖先要考虑深度,所以需要depth[N]

纯裸的一道lca题目

c 复制代码
#include<bits/stdc++.h>

using namespace std;
int n,m;
const int N=4e4+10,M=2*N;
int h[N],e[M],ne[M],idx;
int depth[N],fa[N][16],q[N];

void add(int a,int b){
  e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}

void bfs(int root){
  memset(depth,0x3f,sizeof depth);
  depth[0]=0,depth[root]=1;
  int hh=0,tt=1;
  q[0]=root;
  while(hh<tt){
    int t=q[hh++];
    //if(hh==N) hh=0;
    
    for(int i=h[t];i!=-1;i=ne[i]){
      int j=e[i];
      if(depth[j]>depth[t]+1){
        depth[j]=depth[t]+1;
        fa[j][0]=t;
        
        q[tt++]=j;
        //if(tt==N) tt=0;
				
        for(int k=1;k<=15;k++){
          fa[j][k]=fa[fa[j][k-1]][k-1];
        }
      }
    }
  }
}

int lca(int a,int b){
  
  if(depth[a]<depth[b]) swap(a,b);
  for(int i=15;i>=0;i--){
    if(depth[fa[a][i]]>=depth[b]){
       a=fa[a][i];
    }
  }
  if(a==b) return a;
  for(int i=15;i>=0;i--){
    if(fa[a][i]!=fa[b][i]){
      a=fa[a][i];
      b=fa[b][i];
    }
  }
  return fa[a][0];
}

signed main(){
  cin>>n;
  int root=0;
  memset(h,-1,sizeof h);
  for(int i=0;i<n;i++){
    int a,b;
    cin>>a>>b;
    if(b==-1) root=a;
    else{
      add(a,b);
      add(b,a);
    }
  }
  bfs(root);
  cin>>m;
  for(int i=0;i<m;i++){
    int a,b;
    cin>>a>>b;
    int res=lca(a,b);
    if(res==a) puts("1");
    else if(res==b) puts("2");
    else puts("0");
  }
  return 0;
}
2.距离

大模拟一遍这道题就能把tarjan算法搞清楚了

c 复制代码
#include<bits/stdc++.h>

using namespace std;
int n,m;
typedef pair<int,int> PII;
const int N=1e4+10,M=2*N+10;
int h[N],e[M],ne[M],w[M],idx;
int p[N],st[N],dist[N];
int res[M];
vector<PII> query[N];

int find(int x){
  if(x!=p[x]) p[x]=find(p[x]);
  return p[x];
}

void add(int a,int b,int c){
  e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}

//深搜,预处理根节点到其他节点的距离
void dfs(int u,int fa){
  
  for(int i=h[u];i!=-1;i=ne[i]){
    int j=e[i];
    if(j!=fa){
      dist[j]=dist[u]+w[i];
    	dfs(j,u);
    }
  }
}

//深搜在每个点做targan,然后更新已经回溯的节点并且被询问到的节点到当前节点的距离;
void tarjan(int u){
  st[u]=1;
  for(int i=h[u];i!=-1;i=ne[i]){
    int j=e[i];
    if(!st[j]){
      st[u]=1;
      tarjan(j);
      p[j]=u;
    }
  }
  
  for(auto item : query[u]){
    int y=item.first,id=item.second;
    if(st[y]==2){
      int anc=find(y);
      res[id]=dist[u]+dist[y]-2*dist[anc];
    }
  }
  st[u]=2;
}

			
signed main(){
  cin>>n>>m;
  memset(h,-1,sizeof h);
  for(int i=1;i<=n;i++) p[i]=i;
  for(int i=0;i<n-1;i++){
    int a,b,c;
    cin>>a>>b>>c;
    add(a,b,c);
    add(b,a,c);
  }
  for(int i=0;i<m;i++){
   	int a,b;
    cin>>a>>b;
    query[a].push_back({b,i});
    query[b].push_back({a,i});
  }
  
  dfs(1,-1);
  tarjan(1);
  
  for(int i=0;i<m;i++) cout<<res[i]<<endl;
  return 0;
}
3.次小生成树

思考:

  1. 为什么会和最近公共祖先有关

步骤:

  • 先kruskal算法求出最小生成树权值之和
  • 预处理一遍在最小生成树里的边的depth[i]和 f a [ i ] [ j ] fa[i][j] fa[i][j]
  • lca处理枚举最小生成树外的一条边加进最小生成树中产生的环,算出该环的最大边和次大边,进而判断该附加边是否的加入以及最大边(次大边)的移除是否会产生次小生成树,枚举最小生成树外的一条边就可以找到严格的次小生成树
c 复制代码
#include<bits/stdc++.h>

using namespace std;
typedef long long LL;
int n,m;
const int N=1e5+10,M=3e5+10,INF=0x3f3f3f3f;
int h[N],e[M],ne[M],w[M],idx;
int p[N],dist[N],st[N],q[N];
int depth[N],fa[N][17];
int	d1[N][17],d2[N][17];

int find(int x){
  if(x!=p[x]) p[x]=find(p[x]);
  return p[x];
}
  
struct edge{
  int a,b,c;
  bool used;
  bool operator<(const edge& M)const{
    return c<M.c;
  }
}es[M];

void add(int a,int b,int c){
  e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}

LL kruskul(){
  LL sum=0;
  sort(es,es+m);
  for(int i=0;i<m;i++){
    int a,b,c;
    a=es[i].a,b=es[i].b,c=es[i].c;
    int x=find(a),y=find(b);
    if(x!=y){
      p[x]=y;
      sum+=c;
      es[i].used=1;
    } 
  }
  return sum;
}

void build(){
 	memset(h,-1,sizeof h);
  for(int i=0;i<m;i++){
    if(es[i].used){
      int a=es[i].a,b=es[i].b,c=es[i].c;
      add(a,b,c);
      add(b,a,c);
    }
  }
}

void bfs(){
  
  memset(depth,0x3f,sizeof depth);
  depth[0]=0,depth[1]=1;
  q[0]=1;
  int hh=0,tt=1;
  while(hh<tt){
    int t=q[hh++];
    
    for(int i=h[t];i!=-1;i=ne[i]){
      int j=e[i];
      if(depth[j]>depth[t]+1){
        depth[j]=depth[t]+1;
        q[tt++]=j;
        fa[j][0]=t;
        
        d1[j][0]=w[i],d2[j][0]=-INF;
        
        for(int k=1;k<=16;k++){
          int anc=fa[j][k-1];
          fa[j][k]=fa[anc][k-1];
          int distance[4]={d1[j][k-1],d2[j][k-1],d1[anc][k-1],d2[anc][k-1]};
          d1[j][k]=d2[j][k]=-INF;
          for(int u=0;u<4;u++){
            int d=distance[u];
            if(d>d1[j][k]){
              d2[j][k]=d1[j][k];
              d1[j][k]=d;
              
            }
            else if(d!=d1[j][k]){
              if(d>d2[j][k]){
                d2[j][k]=d;
              }
            }
          }
        }
      }
    }
  }
}

int lca(int a,int b,int c){
  int distance[N*2];
  int cnt=0;
  if(depth[a]<depth[b]) swap(a,b);
  for(int i=16;i>=0;i--){
    //这里注意判断的是a的第2^i跳到的点的深度和b深度
    if(depth[fa[a][i]]>=depth[b]){
      distance[cnt++]=d1[a][i];
      distance[cnt++]=d2[a][i];
      a=fa[a][i];
    }
  }
  
  if(a!=b){
    for(int i=16;i>=0;i--){
      if(fa[a][i]!=fa[b][i]){
        distance[cnt++]=d1[a][i];
        distance[cnt++]=d2[a][i];
        distance[cnt++]=d1[b][i];
        distance[cnt++]=d2[b][i];
        a=fa[a][i];
        b=fa[b][i];
      }
    }
    distance[cnt++]=d1[a][0];
    distance[cnt++]=d1[b][0];
  }
  
  int dist1=-INF,dist2=-INF;
  for(int i=0;i<cnt;i++){
    if(dist1<distance[i]){
      dist2=dist1;
      dist1=distance[i];
    }
    else if(dist1!=distance[i]&&distance[i]>dist2){
      dist2=distance[i];
    }
  }
  if(c>dist1) return c-dist1;
  if(c>dist2) return c-dist2;
  return INF;
}

signed main(){
  cin>>n>>m;
  for(int i=1;i<=n;i++) p[i]=i;
  for(int i=0;i<m;i++){
    int a,b,c;
    cin>>a>>b>>c;
    es[i]={a,b,c};
  }
  LL sum = kruskul();
  build();
  bfs();
  LL res=1e18;
  for(int i=0;i<m;i++){
    if(!es[i].used){
      int a=es[i].a,b=es[i].b,c=es[i].c;
      res=min(res,sum+lca(a,b,c));
    }
  }
  //cout<<sum<<endl;
  cout<<res<<endl;
  return 0;
}
4.闇の連鎖
c 复制代码
#include<bits/stdc++.h>

using namespace std;
int n,m;
const int N=1e5+10,M=2e5+10;
int h[N],e[M],ne[M],w[M],idx;
int d[N],depth[N],fa[N][17],q[N];
int ans;

void add(int a,int b){
  e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}

void bfs(){
  memset(depth,0x3f,sizeof depth);
  depth[0]=0,depth[1]=1;
  int hh=0,tt=1;
  q[0]=1;
  
  while(hh<tt){
    int t=q[hh++];
    
    for(int i=h[t];i!=-1;i=ne[i]){
      int j=e[i];
      if(depth[j]>depth[t]+1){
        depth[j]=depth[t]+1;
        q[tt++]=j;
        fa[j][0]=t;
        
        for(int k=1;k<=16;k++){
          int anc=fa[j][k-1];
          fa[j][k]=fa[anc][k-1];
        }
      }
    }
  }
}

int lca(int a,int b){
  if(depth[a]<depth[b]) swap(a,b);
  for(int k=16;k>=0;k--){
    if(depth[fa[a][k]]>=depth[b]){
      a=fa[a][k];
    }
  }
  if(a==b) return a;
  if(a!=b){
    for(int k=16;k>=0;k--){
      if(fa[a][k]!=fa[b][k]){
        a=fa[a][k];
        b=fa[b][k];
      }
    }
  }
  return fa[a][0];
}

int dfs(int u,int father){
  int res=d[u];
  
  for(int i=h[u];i!=-1;i=ne[i]){
    int j=e[i];
    if(j!=father){
      int s=dfs(j,u);
      if(s==0) ans+=m;
      if(s==1) ans+=1;
      res+=s;
    }
  }
  return res;
}

signed main(){
  memset(h,-1,sizeof h);
  cin>>n>>m;;
  for(int i=0;i<n-1;i++){
    int a,b;
    cin>>a>>b;
    add(a,b);
    add(b,a);
  }
  bfs();//先预处理出每一条,才能求lca啊老铁
  for(int i=0;i<m;i++){
    int a,b;
    cin>>a>>b;
    int p=lca(a,b);
    d[a]++,d[b]++,d[p]-=2;
  }
  
  
  dfs(1,-1);
  cout<<ans<<endl;
  return 0;
}
相关推荐
xiaoshiguang33 小时前
LeetCode:222.完全二叉树节点的数量
算法·leetcode
爱吃西瓜的小菜鸡3 小时前
【C语言】判断回文
c语言·学习·算法
别NULL3 小时前
机试题——疯长的草
数据结构·c++·算法
TT哇3 小时前
*【每日一题 提高题】[蓝桥杯 2022 国 A] 选素数
java·算法·蓝桥杯
yuanbenshidiaos4 小时前
C++----------函数的调用机制
java·c++·算法
唐叔在学习4 小时前
【唐叔学算法】第21天:超越比较-计数排序、桶排序与基数排序的Java实践及性能剖析
数据结构·算法·排序算法
ALISHENGYA5 小时前
全国青少年信息学奥林匹克竞赛(信奥赛)备考实战之分支结构(switch语句)
数据结构·算法
chengooooooo5 小时前
代码随想录训练营第二十七天| 贪心理论基础 455.分发饼干 376. 摆动序列 53. 最大子序和
算法·leetcode·职场和发展
jackiendsc5 小时前
Java的垃圾回收机制介绍、工作原理、算法及分析调优
java·开发语言·算法
游是水里的游6 小时前
【算法day20】回溯:子集与全排列问题
算法