Tarjan离线算法 (LCA最近公共祖先)

Tarjan离线算法是利用并查集和DFS来达到离线处理的目的
我们都知道,对于一棵树,后序遍历一遍,访问它的根的时机一定是后与它的孩子的。换一句话,当开始遍历它的根节点的时候,它遍历过的孩子的公共祖先一定是这个根而这也就成为了我们解题的思想。
由于是需要对整树进行DFS,所以Tarjan需要在所有信息都输入完毕后才能进行操作,这也是为什么要离线的原因

所谓在线算法,就是每输入一个查询,就进行一次算法操作,并输出查询结果。离线算法,就是对所有的查询,用一个表结构存储起来,一次算法处理掉所有的查询,最后O(1)的复杂度输出。

  1. 找到入度为0的点u(根节点)
  2. Tarjan(u)
    1. 访问r的每个子结点v,如果该子结点v未被访问,Tarjan(v)
    2. 将c加入到父亲结点u中
    3. 标记v为已访问
  3. 处理关于u的查询,若查询(u,v)中的v已遍历过,则LCA(u,v)= v所在的集合的祖先,即Find(v);

    最后来一道例题吧
    POJ1470
    一道模板题,直接放代码了

#include <map>
#include <queue>
#include <cmath>
#include <cstdio>
#include <string>
#include <cstring>
#include <iostream>
#include <algorithm>
#define pb push_back
#define lowbit(a) (a&(-a))
#define fori(a) for(int i=0;i<a;i++)
#define forj(a) for(int j=0;j<a;j++)
#define ifor(a) for(int i=1;i<=a;i++)
#define jfor(a) for(int j=1;j<=a;j++)
#define _mem(a,b) memset(a,0,(b+3)<<2)
#define mem(a,b) memset(a,b,sizeof(a))
#define IN freopen("in.txt","r",stdin)
#define OUT freopen("out.txt","w",stdout)
#define IO do{\
    ios::sync_with_stdio(false);\
    cin.tie(0);\
    cout.tie(0);}while(0)
using namespace std;
typedef long long ll;
const int maxn =  1e4+10;
const int MAXN = 1e6+10;
const int INF = 0x3f3f3f3f;
const int inf = 0x3f;
const double EPS = 1e-7;
const double Pi = acos(-1);
const int MOD = 1e9+7;

vector <int >a[maxn];   //邻接表存图
vector <int >q[maxn];   //邻接表存查询
int p[maxn];            //并查集中的father数组
bool v[maxn];           //是否被访问
bool root[maxn];        //根节点
int res[maxn];          //答案数组
int n;
int Find(int x){        
    if(p[x]!=x)
        p[x] = Find(p[x]);
    return p[x];
}
void join(int root1, int root2)
{
    int x, y;
    x = Find(root1);
    y = Find(root2);
    if(x != y)
        p[y] = x;
}


void Tarjan(int u){
    fori(a[u].size())
        if(!v[a[u][i]]){
            Tarjan(a[u][i]);
            join(u,a[u][i]);
            v[a[u][i]] = true;
        }
    fori(q[u].size()){          //对于所有查询,如果i点已被访问过,则能查询其公共祖先
        if(v[q[u][i]] == true)
            res[Find(q[u][i])]++;
    }
}

void Init(){                    //初始化
    fori(n+1){
        a[i].clear();
        q[i].clear();
        p[i] = i;
        res[i] = 0;
        v[i] = 0;
        root[i] = 1;
    }
}

int main()
{
    //IO;
    //IN;
    int bufx,bufy;
    int x(1);
    int t;
    while(cin >> n){
        Init();
        fori(n){
            scanf("%d:(%d)",&bufx,&bufy);
            while(bufy--){
                scanf("%d",&t);
                a[bufx].pb(t);
                root[t] = false;
            }
        }
        scanf("%d",&t);
        while(t--){
            scanf(" (%d%d)",&bufx,&bufy);
            q[bufx].pb(bufy);
            q[bufy].pb(bufx);
        }

        ifor(n){                //找出根节点
            if(root[i]){
                Tarjan(i);
                break;
            }
        }
        ifor(n+1){
             if(res[i])
                cout << i<<":"<<res[i]<<endl;
        }
    }
    return 0;

}

猜你喜欢

转载自blog.csdn.net/bestsort/article/details/81388570