一道好题啊,加深了我对lca的理解。

/*
*State: POJ3694  Accepted    6924K    407MS    C++    2466B
*题目大意:
*        给一个无向图,该图只有一个连通分量。然后查询q次,q < 1000,
*        求每次查询就增加一条边,求剩余桥的个数。
*解题思路:
*        求出搜索树的时间戳dfn,发现两点的lca的时间戳大于左子树,小于右子树。
*        之后可以通过合并连通分量的方式来计算剩余的桥的个数。合并连通分量的
*        方法,发现用并查集来合并,要方便得多啊,很容易合并掉两个大块。所以
*        就用并查集来合并连通分量了。
*解题感想;
*        原来lca还可以这么个玩法。
*/

用并查集实现加边过程:

View Code
#include <iostream>
#include <vector>
#include <cmath>
#include <cstring>
#include <cstdio>
#include <vector>
using namespace std;

const int MAXN = 100005;
const int MAXE = 200005;
typedef struct _node
{
    int v, next;
}N;
N edge[2 * MAXE];

int dfn[MAXN], low[MAXN];
int step, head[MAXN], cntEdge;
int bridgeNum, pre[MAXN];

typedef struct _uqnode
{
    int p;
}U;
U uqSet[MAXN];

void init()
{
    step = cntEdge = bridgeNum = 0;
    for(int i = 0; i < MAXN; i++)
    {
        uqSet[i].p = i;
        head[i] = -1;
        dfn[i] = low[i] = -1;
    }
}

int findSet(int x)
{
    if(x != uqSet[x].p)
        uqSet[x].p = findSet(uqSet[x].p);
    return uqSet[x].p;
}

bool Union(int x, int y)
{
    int a = findSet(x);
    int b = findSet(y);
    if(a == b)
        return false;
    uqSet[b].p = a;
    return true;
}


//题目好像没有提到重边
void addEdge(int u, int v)
{
    edge[cntEdge].v = v;
    edge[cntEdge].next = head[u];
    head[u] = cntEdge++;

    edge[cntEdge].v = u;
    edge[cntEdge].next = head[v];
    head[v] = cntEdge++;
}

void tarjan_scc(int n, int father)
{
    dfn[n] = low[n] = ++step;
    int flag = 0;

    for(int f = head[n]; f != -1; f = edge[f].next)
    {
        int son = edge[f].v;
        if(son == father && !flag)
        {
            flag = 1;
            continue;
        }
        if(dfn[son] == -1)
        {
            pre[son] = n;
            tarjan_scc(son, n);
            low[n] = min(low[n], low[son]);

            if(low[son] > dfn[n])
            {
                bridgeNum++;
            }
            else
                Union(n, son);
        }
        else
            low[n] = min(low[n], dfn[son]);
    }
}

int lca(int u, int v)
{
    if(findSet(u) == findSet(v))
        return bridgeNum;

    if(dfn[u] > dfn[v])
    {
        u = u ^ v;
        v = u ^ v;
        u = u ^ v;
    }

    while(dfn[u] < dfn[v])
    {
        if(Union(pre[v], v))
            bridgeNum--;
        v = pre[v];
    }
    while(u != v)
    {
        if(Union(u, pre[u]))
            bridgeNum--;
        u = pre[u];
    }

    return bridgeNum;
}

int main(void)
{
#ifndef ONLINE_JUDGE
    freopen("in.txt", "r", stdin);
#endif

    int n, m, cas_c = 1;
    while(scanf("%d %d", &n, &m), n || m)
    {
        init();

        int u, v;
        for(int i = 0; i < m; i++)
        {
            scanf("%d %d", &u, &v);
            addEdge(u, v);
        }
        pre[1] = 1;
        tarjan_scc(1, 1);
        
        int q;
        scanf("%d", &q);
        printf("Case %d:\n", cas_c++);
        for(int i = 0; i < q; i++)
        {
            scanf("%d %d", &u, &v);
            //查询
            printf("%d\n", lca(u, v));
        }
    }
    return 0;
}

用数组实现加边过程:

View Code
#include <iostream>
#include <vector>
#include <cmath>
#include <cstring>
#include <cstdio>
#include <vector>
using namespace std;

const int MAXN = 100005;
const int MAXE = 200005;
typedef struct _node
{
    int v, next;
}N;
N edge[2 * MAXE];

int dfn[MAXN], low[MAXN];
int step, head[MAXN], cntEdge;
int bridgeNum, pre[MAXN];
int bridge[MAXN], myS[MAXN], top, id[MAXN], scc;

typedef struct _uqnode
{
    int p;
}U;
U uqSet[MAXN];

void init()
{
    top = 0;
    scc = 1;
    step = cntEdge = bridgeNum = 0;
    for(int i = 0; i < MAXN; i++)
    {
        id[i] = -1;
        bridge[i] = -1;
        uqSet[i].p = i;
        head[i] = -1;
        dfn[i] = low[i] = -1;
    }
}

//题目好像没有提到重边
void addEdge(int u, int v)
{
    edge[cntEdge].v = v;
    edge[cntEdge].next = head[u];
    head[u] = cntEdge++;

    edge[cntEdge].v = u;
    edge[cntEdge].next = head[v];
    head[v] = cntEdge++;
}

void tarjan_scc(int n, int father)
{
    dfn[n] = low[n] = ++step;
    int flag = 0;
    myS[top++] = n;
    for(int f = head[n]; f != -1; f = edge[f].next)
    {
        int son = edge[f].v;
        if(son == father && !flag)
        {
            flag = 1;
            continue;
        }
        if(dfn[son] == -1)
        {
            pre[son] = n;
            tarjan_scc(son, n);
            low[n] = min(low[n], low[son]);

            if(low[son] > dfn[n])
            {
                bridgeNum++;
                bridge[son] = 1;
            }
        }
        else
            low[n] = min(low[n], dfn[son]);
    }
    if(low[n] == dfn[n])
    {
        int tmp;
        do
        {
            tmp = myS[--top];
            id[tmp] = scc;

        }while(top != 0 && tmp != n);
        scc++;
    }
}

int lca(int u, int v)
{
    if(id[u] == id[v])
        return bridgeNum;

    if(dfn[u] > dfn[v])
    {
        u = u ^ v;
        v = u ^ v;
        u = u ^ v;
    }

    while(dfn[u] < dfn[v])
    {
        if(bridge[v] == 1)
        {
            bridgeNum--;
            bridge[v] = -1;
        }
        v = pre[v];
    }

    while(u != v)
    {
        if(bridge[u] == 1)
        {
            bridgeNum--;
            bridge[u] = -1;
        }
        u = pre[u];
    }

    return bridgeNum;
}

int main(void)
{
#ifndef ONLINE_JUDGE
    freopen("in.txt", "r", stdin);
#endif

    int n, m, cas_c = 1;
    while(scanf("%d %d", &n, &m), n || m)
    {
        init();

        int u, v;
        for(int i = 0; i < m; i++)
        {
            scanf("%d %d", &u, &v);
            addEdge(u, v);
        }
        pre[1] = 1;
        tarjan_scc(1, 1);
        
        int q;
        scanf("%d", &q);
        printf("Case %d:\n", cas_c++);
        for(int i = 0; i < q; i++)
        {
            scanf("%d %d", &u, &v);
            //查询
            printf("%d\n", lca(u, v));
        }
    }
    return 0;
}
posted on 2012-08-18 09:30  cchun  阅读(1721)  评论(0编辑  收藏  举报