最近公共祖先离线加在线倍增

26 篇文章 0 订阅

http://blog.csdn.net/hnust_xiehonghao/article/details/9109295
离线算法其实就是dfs加并查集,在遍历图的时候进行查询,过程中不断地更新节点的父节点的值,确实是非常巧妙的,上面那个博客有很详细的介绍和说明
http://blog.csdn.net/jarjingx/article/details/8183240
在线倍增法,这个思想很容易应用到其他领域,我记得cf有一道题就是利用的这个思想,首先进行预处理,每个节点跳j步到达哪个节点,然后就是先把二者的深度调整为同样,然后利用二进制的性质从大到小,确定二者最后应该跳多少步,正好父节点就是最近公共祖先,这个方法非常值得学习,具体可以参看上面的博客

#include<stdio.h>
#include<string.h>
#include<vector>
using namespace std;
#define Size  11111
struct Edge
{
    int y,val;
}temp;
struct Query
{
    int y,id;
}mid;
int pare[Size],ance[Size],vis[Size],dis[Size],rank[Size],ans[1000000+100],n,m,c,tree[Size];
vector<struct Query>que[Size];
vector<struct Edge>node[Size];
void init()
{
    int i;
    for(i=0;i<=n;i++)
    {
        vis[i]=0;
        pare[i]=i;
        dis[i]=0;
        rank[i]=1;
        que[i].clear();
        node[i].clear();
    }
    memset(ans,-1,sizeof(ans));
}
int find(int x)
{
    return pare[x]==x?x:pare[x]=find(pare[x]);
}
/*
void Union(int x,int y)
{
x=find(x);
y=find(y);
if(x!=y)
{
if(rank[x]>rank[y])
{
rank[x]+=rank[y];
pare[y]=x;
}
else
{
rank[y]+=rank[x];
pare[x]=y;
}
}
}
*/
void LCA(int root,int d,int k)//k表示是以第k个点作为根的树
{
    int i,sz,nd1,nd2;
    vis[root]=1; //已经遍历过的点 要标记一下 不要
    tree[root]=k;dis[root]=d;
    // ance[root]=root;
    sz=node[root].size();
    for(i=0;i<sz;i++)
    {
        nd2=node[root][i].y;
        if(!vis[nd2])
        {
            LCA(nd2,d+node[root][i].val,k);
            // Union(node[root][i].y,root);//用带rank的幷查集操作答案不对 不知道why
            int w=find(nd2),m=find(root);
            if(w!=m)
            {
               pare[w]=m;//这样才对
            }
            //ance[find(node[root][i].y)]=root;
        }
    }
    sz=que[root].size();
    for(i=0;i<sz;i++)
    {
        nd1=root;
        nd2=que[root][i].y;
        if(vis[nd2]&&tree[nd1]==tree[nd2])//如果 nd1 nd2 的跟是同一个点 则是同一棵树上的
        {
            ans[que[root][i].id]=dis[nd1]+dis[nd2]-2*dis[find(nd2)];
        }
    }
}
int main()
{
    int i,j,x,y,val;
    while(scanf("%d %d %d",&n,&m,&c)!=EOF)
    {
        init();
        for(i=0;i<m;i++)
        {
            scanf("%d %d %d",&x,&y,&val);
            if(x!=y)
            {
                temp.y=y;temp.val=val;
                node[x].push_back(temp);
                temp.y=x;
                node[y].push_back(temp);//路是2个方向都可以通行的
            }
        }
        for(i=0;i<c;i++)
        {
            scanf("%d %d",&x,&y);
            mid.id=i;
            mid.y=y;
            que[x].push_back(mid);
            mid.y=x;
            que[y].push_back(mid);
        }
        for(i=1;i<=n;i++)
        {
            LCA(i,0,i);//以每一个节点作为根节点去深度搜索  找出每个点作为根的所有最近公共祖先
        }
        for(i=0;i<c;i++)
        {
            if(ans[i]==-1)
                printf("Not connected\n");
            else
                printf("%d\n",ans[i]);
        }
    }
    return 0;
}
/*本题给的是一个森林 而不是一颗树,由于在加入边的时候,我们让2个方向都能走 这样就
形成了一个强连通的快,  对于这个快来说,不管从快上那点出发 都可以遍历这个快上的所
有的点,且相对距离是一样的*/
 #include<iostream>  
 #include<cstdio>  
 #include<cstring>  
 #include<algorithm>  
 #include<vector>  
 using namespace std; 
 #define N 10010  
 #define M 20  
 int d[N],f[N][M]; 
 vector<int>ch[N]; 
 void dfs(int x){//求出所有结点深度  
     d[x]=d[f[x][0]]+1; 
     for(int i=1;i<M;i++)f[x][i]=f[f[x][i-1]][i-1];//倍增祖先  
     for(int i=ch[x].size()-1;i>=0;i--) 
         dfs(ch[x][i]);//遍历儿子  
 } 
int lca(int x,int y){//求x,y最小公共祖先  
         if(d[x]<d[y])swap(x,y);//保证x的深度不小于y  
         int k=d[x]-d[y]; 
         for(int i=0;i<M;i++) 
             if((1<<i)&k) 
                 x=f[x][i]; 
     if(x==y)return x;//x==y时为最小公共祖先  
         for(int i=M-1;i>=0;i--) 
             if(f[x][i]!=f[y][i]){ 
                 x=f[x][i];y=f[y][i]; 
             } 
     return f[x][0]; 
 } 
 int main(){ 
     int T,n,i,x,y; 
     scanf("%d",&T); 
     while(T--){ 
         scanf("%d",&n); 
         memset(d,0,sizeof(d)); 
         memset(f,0,sizeof(f)); 
         memset(ch,0,sizeof(ch)); 
         for(i=1;i<n;i++){ 
             scanf("%d%d",&x,&y); 
             ch[x].push_back(y); 
             f[y][0]=x; 
         } 
         for(i=1;i<=n;i++) 
             if(f[i][0]==0){//找到根遍历  
                 dfs(i);break; 
             } 
             scanf("%d%d",&x,&y); 
             printf("%d\n",lca(x,y)); 
     } 
 return 0; 
 } 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值