1. 程式人生 > >LCA-tarjan understand 2

LCA-tarjan understand 2

pid strong edge lca arc repl root memset html

下面是一個最基礎的LCA題目 http://poj.org/problem?id=1330

赤裸裸的 題意 輸入cas 後 有cas組數據 輸入 n 再輸入n-1 條邊 之後輸入x y 問x y的最近公共祖先是什麽

#include<stdio.h>
#include<vector>
#include<string.h>
using namespace std;
#define Size 11111  //節點個數

vector<int> node[Size],que[Size];
int n,pare[Size],anse[Size],in
[Size],rank[Size]; int vis[Size]; void init() { int i; for(i=1;i<=n;i++) { node[i].clear(); que[i].clear(); rank[i]=1; pare[i]=i;/// } memset(vis,0,sizeof(vis)); memset(in,0,sizeof(in)); memset(anse,0,sizeof(anse)); } int find(int nd)//
並查集操作 不解釋 { return pare[nd]==nd?nd:pare[nd]=find(pare[nd]); } int Union(int nd1,int nd2)//並查集操作 不解釋 { int a=find(nd1); int b=find(nd2); if(a==b) return 0; else if(rank[a]<=rank[b]) { pare[a]=b; rank[b]+=rank[a]; } else { pare[b]=a; rank[a]
+=rank[b]; } return 1; } void LCA(int root) { int i,sz; anse[root]=root;//首先自成一個集合 sz=node[root].size(); for(i=0;i<sz;i++) { LCA(node[root][i]);//遞歸子樹 Union(root,node[root][i]);//將子樹和root並到一塊 anse[find(node[root][i])]=root;//修改子樹的祖先也指向root } vis[root]=1; sz=que[root].size(); for(i=0;i<sz;i++) { if(vis[que[root][i]]) { printf("%d\n",anse[find(que[root][i])]);///root和que[root][i]所表示的值的最近公共祖先 return ; } } return ; } int main() { int cas,i; scanf("%d",&cas); while(cas--) { int s,e; scanf("%d",&n); init(); for(i=0;i<n-1;i++) { scanf("%d %d",&s,&e); if(s!=e) { node[s].push_back(e); // node[e].push_back(s); in[e]++; } } scanf("%d %d",&s,&e); que[s].push_back(e); que[e].push_back(s); for(i=1;i<=n;i++) if(in[i]==0) break;//尋找根節點 // printf("root=%d\n",i); LCA(i); } return 0; }

之後來個加強版

http://acm.hdu.edu.cn/showproblem.php?pid=4547 CD操作 hdu4547

思路:

  求出a和b的最近公共祖先,然後分4種情況討論

  ①. a和b有一個公共祖先c,則用 c時間戳-a的時間戳+1(1步可以直接從c到b)

  ②. a是b的祖先,則只用1步就可以到達b點

  ③. b是a的祖先,則用a的時間戳-b的時間戳

  ④. a和b是同一個點,則答案是0

參考 http://www.cnblogs.com/Griselda/archive/2013/06/05/3119265.html

#include<stdio.h>
#include<vector>
#include<string.h>
#include<map>
#include<math.h>
#include<string>
using namespace std;
#define Size 111111  //節點個數
struct Query
{
    int nd,id;
}temp;
struct out
{
    int s,e;
}out[Size];
vector<int> node[Size];
vector<struct Query>que[Size];
int n,m,pare[Size],ance[Size],in[Size],rank[Size],dis[Size],ans[Size],vis[Size];
map<string,int>mp;
void init()
{
    int i;
    for(i=1;i<=n;i++)
    {
        node[i].clear();
        que[i].clear();
        rank[i]=1;
        pare[i]=i;///
    }
    memset(vis,0,sizeof(vis));
    memset(in,0,sizeof(in));
    memset(ance,0,sizeof(ance));
    memset(dis,0,sizeof(dis));
    mp.clear();
}
int aabs(int aa)
{
     if(aa>0) return aa;
     else return -aa;
}
int find(int nd)//並查集操作  不解釋
{
    return pare[nd]==nd?nd:pare[nd]=find(pare[nd]);
}
int Union(int nd1,int nd2)//並查集操作  不解釋
{
    int a=find(nd1);
    int b=find(nd2);
    if(a==b) return 0;
    else if(rank[a]<=rank[b])
    {
        pare[a]=b;
        rank[b]+=rank[a];
    }
    else
    {
        pare[b]=a;
        rank[a]+=rank[b];
    }
    return 1;
}
void LCA(int root,int num)
{
    int i,sz;
    ance[root]=root;//首先自成一個集合
    dis[root]=num;
    sz=node[root].size();
    for(i=0;i<sz;i++)
    {
           LCA(node[root][i],num+1);//遞歸子樹
           Union(root,node[root][i]);//將子樹和root並到一塊
         ance[find(node[root][i])]=root;//修改子樹的祖先也指向root
    }
    vis[root]=1;
    sz=que[root].size();
    for(i=0;i<sz;i++)
    {
        int nd1,nd2,idx,ancestor;
        nd1=root;nd2=que[root][i].nd;idx=que[root][i].id;
            if(vis[nd2])
            {
                  ans[idx]=ance[find(nd2)];
            }
    }
     return ;
}

int main()
{
    int cas,i;
    scanf("%d",&cas);
    while(cas--)
    {
        char  ss[100],ee[100];
        int s,e,cnt=1;
        scanf("%d %d",&n,&m);
        init();
        for(i=0;i<n-1;i++)
        {
            scanf("%s %s",ee,ss);
            if(mp.find(ss)==mp.end())
            {
                 s=cnt;mp[ss]=cnt++;
            }
            else s=mp[ss];
            if(mp.find(ee)==mp.end())
            {
                e=cnt;mp[ee]=cnt++;
            }
            else  e=mp[ee];
            if(s!=e)
            {
                node[s].push_back(e);
                in[e]++;
            }
        }
        for(i=0;i<m;i++)
        {
           scanf("%s %s",ss,ee);
           s=mp[ss];e=mp[ee];
           out[i].s=s;out[i].e=e;
           temp.nd=e;temp.id=i;
           que[s].push_back(temp);
           temp.nd=s;temp.id=i;
           que[e].push_back(temp);
        }
        for(i=1;i<=n;i++)  if(in[i]==0) break;//尋找根節點
        LCA(i,0);
        for(i=0;i<m;i++)
        {
            if(out[i].s==out[i].e)
                printf("0\n");
            else
                if(out[i].s==ans[i])
                      printf("1\n");
            else if(out[i].e==ans[i])
                printf("%d\n",dis[out[i].s]-dis[ans[i]]);
            else
            printf("%d\n",dis[out[i].s]-dis[ans[i]]+1);
        }
    }
    return 0;
}

hdu 2874

http://acm.hdu.edu.cn/showproblem.PHP?pid=2874

題目大意: 給你一個n個節點m條邊的森林,再給定q個查詢,每次查詢森林裏兩個點的最近距離。n ,m <= 10000,q <= 100萬

本題和標準的LCA模板應用有了不小的區別 卻可以讓人更加透徹的看清LCA的思路 而且本題沒有必要去求出公共祖先

#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個方向都能走 這樣就
形成了一個強連通的快,  對於這個快來說,不管從快上那點出發 都可以遍歷這個快上的所
有的點,且相對距離是一樣的*/

LCA-tarjan understand 2