POJ 3694 Network(双连通分量缩点 + 路径压缩)

题目链接:Click here~~

题意:

n 个点 m 条边的无向图,之后有若干次操作,问每次添加一条边后,图上还剩多少个桥边(操作是累积的)。

解题思路:

先将无向图的 边-双连通分量 缩点,缩点后重新建图,则变为一颗树,树的每条边就可以看做桥边。不妨设每次添加的边为 <u,v> 。

1> 如果之前 u,v 在同一个双连通分量里,则显然不会对结果产生影响。

2> 如果不在,相当于在树中选取两点,连了一条边,从而形成一个环,环中的点会变成一个双连通分量。对结果产生的影响取决于环上的边数。

不难发现,这个环的路径就是: u -> LCA(u,v) -> v 。

如果操作不累积,容易想到重新建图后先转化成一棵树,然后预处理出每个点的深度,离线预处理出两两的 LCA,影响就是2*dep[lca] - dep[u] - dep[v]。

但此题是操作累积的,就不能这样做了。搜了题解,大概懂别人的思路了。

首先,找 LCA(u,v) 相当于 u,v 两个点一起向上爬,直到爬到同一个点 w 即为这两点的 LCA,统计共爬了多少边。我们把这种做法叫做 climb 吧。

我们来考虑两个点爬到 LCA 后,会对之后的操作产生什么影响。

之后的点如果再次爬之前爬过的路径,不会再对结果产生影响了。

既然爬之前爬过的路径这么费力不讨好,是不是能够想到一个好的方法,让它一次登顶?

于是这点就可以很巧妙的和并查集的路径压缩联系到一起。大家看代码体会吧。

#include <stack>
#include <vector>
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;

template<int N,int M>
struct Graph{
    int top;
    struct Vertex{
        int head;
    }V[N];
    struct Edge{
        int v,next;
    }E[M];
    void init(){
        memset(V,-1,sizeof(V));
        top = 0;
    }
    void add_edge(int u,int v){
        E[top].v = v;
        E[top].next = V[u].head;
        V[u].head = top++;
    }
};

const int N = 1e5 + 5;

Graph<N,N*4> g,gg;

int dfn[N],low[N],belong[N];
int bccId,dfsId;

bool cutP[N],cutE[N*4];

vector<int> bcc[N];

void dfs(int pre,int u)
{
    dfn[u] = low[u] = ++dfsId;
    int child = 0;
    for(int i=g.V[u].head;~i;i=g.E[i].next){
        int v = g.E[i].v;
        if(v == pre || dfn[v] > dfn[u])
            continue;
        if(!dfn[v]){
            child++;
            dfs(u,v);
            low[u] = min(low[u],low[v]);
            if(pre != -1 && low[v] >= dfn[u] || pre == -1 && child > 1)
                cutP[u] = true;
        }
        else
            low[u] = min(low[u],dfn[v]);
        if(low[v] > dfn[u]){
            cutE[i] = true;
        }
    }
}

void get_bcc_p(int n)
{
    dfsId = bccId = 0;
    memset(dfn,0,sizeof(dfn));
    memset(cutP,false,sizeof(cutP));
    memset(cutE,false,sizeof(cutE));
    memset(belong,-1,sizeof(belong));
    for(int i=1;i<=n;i++)
        if(!dfn[i])
            dfs(-1,i);
}

bool vis[N];

void dfs2(int u)
{
    vis[u] = true;
    belong[u] = bccId;
    bcc[bccId].push_back(u);
    for(int i=g.V[u].head;~i;i=g.E[i].next){
        int v = g.E[i].v;
        if(!vis[v] && !cutE[i] && !cutE[i^1])
            dfs2(v);
    }
}

void get_bcc_e(int n)
{
    bccId = 0;
    memset(vis,false,sizeof(vis));
    for(int i=1;i<=n;i++){
        if(vis[i])
            continue;
        bcc[++bccId].clear();
        dfs2(i);
    }
}

int father[N],dep[N];

void dfss(int u,int depth)
{
    vis[u] = true;
    dep[u] = depth;
    for(int i=gg.V[u].head;~i;i=gg.E[i].next)
    {
        int v = gg.E[i].v;
        if(!vis[v]){
            father[v] = u;
            dfss(v,depth+1);
        }
    }
}

void rebuild(int n)
{
    for(int u=1;u<=n;u++)
        for(int i=g.V[u].head;~i;i=g.E[i].next){
            int v = g.E[i].v;
            if(belong[u] != belong[v])
                gg.add_edge(belong[u],belong[v]),
                gg.add_edge(belong[v],belong[u]);
        }
    memset(vis,false,sizeof(vis));
    father[1] = 0;
    dfss(1,0);
}

namespace ufSet
{
    //const int N = 1e5 + 5;
    int pre[N];
    void init(){
        memset(pre,-1,sizeof(pre));
    }
    int root(int x){
        return pre[x] == -1 ? x : pre[x] = root(pre[x]);
    }
    bool gather(int a,int b){
        int r1 = root(a);
        int r2 = root(b);
        if(r1 == r2)
            return false;
        else
            pre[r1] = r2;
            return true;
    }
}

using namespace ufSet;

 int climb(int a,int b){
    int ret = 0;
    while(1){
        a = root(a);
        b = root(b);
        if(a == b)
            break;
        if(dep[a] > dep[b])
            gather(a,father[a]);
        else
            gather(b,father[b]);
        ++ret;
    }
    return ret;
}

void debug()
{
    for(int i=1;i<=bccId;i++)
        for(int j=0;j<(int)bcc[bccId].size();j++)
            printf("%d->%d%c",bcc[i][j],i,j==(int)bcc[bccId].size()-1?'\n':' ');
}

int main()
{
    int n,m,Q,ncase = 0;
    while(scanf("%d%d",&n,&m),n||m)
    {
        g.init();
        gg.init();
        ufSet::init();
        while(m--)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            g.add_edge(u,v);
            g.add_edge(v,u);
        }
        get_bcc_p(n);
        get_bcc_e(n);
        rebuild(n);
        scanf("%d",&Q);
        int ans = bccId - 1;
        printf("Case %d:\n",++ncase);
        while(Q--)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            ans -= climb(belong[u],belong[v]);
            printf("%d\n",ans);
        }
        puts("");
    }
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值