POJ 3694 Network【Tanjan+LCA+并查集】

题目链接

题目描述
网络管理员管理大型网络。该网络由N台计算机和成对计算机之间的M链路组成。任何一对计算机都通过连续的链接直接或间接连接,因此可以在任何两台计算机之间转换数据。管理员发现某些链接对网络至关重要,因为任何一个链接的故障都可能导致某些计算机之间无法转换数据。他把这种联系称为桥梁。他计划逐一添加一些新链接以消除所有桥梁。 您将通过在添加每个新链接后报告网络中的网桥数来帮助管理员。

输入
输入包含多个测试用例。每个测试用例以包含两个整数N(1≤N≤100,000)和M(N-1≤M≤200,000)的行开始。 以下M行中的每一行包含两个整数A和B(1≤A≠B≤N),表示计算机A和B之间的链接。计算机编号从1到N.保证任何两台计算机都连接在一起最初的网络。 下一行包含一个整数Q(1≤Q≤1,000),这是管理员计划逐个添加到网络的新链接数。 以下Q行的第i行包含两个整数A和B(1≤A≠B≤N),这是连接计算机A和B的第i个新链接。 最后一个测试用例后跟一行包含两个零的行。

输出
对于每个测试用例,打印一行包含测试用例编号(以1开头)和Q行,其中第i行包含一个整数,表示添加第一个i新链接后网络中的网桥数。在每个测试用例的输出后打印一个空行。

 

思路:题目中提到了桥的概念,可以anjan的的判桥,也可以缩点后生成的DAG上的边都是桥(点的个数-1)。
用一种解法是直接暴力利用LCA的思路进行标记边,但是我认为会T,但实际上不会T。
我看到了另外一种解法,就是在树上进行并查集合并(具体思路看代码),我的大概思路和网上基本一直一致,但我的代码不是T就是WA。

自己掉进的坑:
① 在缩点后加了双向边,导致每条边都重复假如了,导致T了。因为在构建原图的时候,已经是双向边,在缩点后只需要加边一次就可以。(单向边Tanjan缩点写多了。。。。。。)
② 在进行树上并查集的时候,每个点都和自己父亲结点合并,保证每个点的根都在本身的上方。

附上代码

///#include<bits/stdc++.h>
///#include<unordered_map>
///#include<unordered_set>
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<cmath>
#include<queue>
#include<set>
#include<stack>
#include<map>
#include<new>
#include<vector>
#define MT(a,b) memset(a,b,sizeof(a));
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const double pai=acos(-1.0);
const double E=2.718281828459;
const int mod=1e9+7;
const int INF=1e9;

int n,m,op;
int sum;

struct node
{
    int e;
    int p;
}load[400005],edge[400005];
int sign;
int head[100005];   ///缩点前
int rehead[100005]; ///缩点后

int grand[100005];  ///在缩点后DAG中每个点的父亲结点
int depth[100005];  ///每个点所在的深度

int dfn[100005],low[100005],time;
int stack_[100005],instack[100005],top;
int belong[100005],cnt;

void add_edge(int s,int e)  ///缩点前加边
{
    load[++sign]=node{e,head[s]};
    head[s]=sign;
}
void add(int s,int e)       ///缩点后加边
{
    edge[++sign]=node{e,rehead[s]};
    rehead[s]=sign;
}

void tanjan(int s,int pre)
{
    dfn[s]=low[s]=++time;
    stack_[++top]=s;
    instack[s]=1;
    int vis=0;
    for(int i=head[s]; i!=-1; i=load[i].p)
    {
        int e=load[i].e;
        if(!dfn[e])
        {
            tanjan(e,s);
            low[s]=min(low[s],low[e]);
        }
        else if(pre==e)     ///判断反边
        {
            if(vis)         ///如果反边不只出现一次,说明出现重边
                low[s]=min(low[s],dfn[e]);
            vis++;
        }
        else
        {
            if(instack[e])
                low[s]=min(low[s],dfn[e]);
        }
    }
    int now;
    if(dfn[s]==low[s])
    {
        cnt++;
        do
        {
            now=stack_[top--];
            instack[now]=0;
            belong[now]=cnt;
        }
        while(now!=s);
    }
    return ;
}

int dfs(int s,int pre)
{
    ///将DAG构成一棵树,方便使用LCA
    int e;
    for(int i=rehead[s]; i!=-1; i=edge[i].p)
    {
        e=edge[i].e;
        if(e!=pre)
        {
            depth[e]=depth[s]+1;
            grand[e]=s;
            dfs(e,s);
        }
    }
}

int p[100005];

int find(int x)
{
    return p[x]==x?x:p[x]=find(p[x]);
}

void lca(int s,int e)
{
    while(s!=e)
    {
        ///每个点的P数组只能向上走(即深度越来越小)
        ///将本身和父亲合并
        if(depth[s]>depth[e])
        {
            sum--;
            p[s]=find(grand[s]);
            s=p[s];
        }
        else if(depth[e]>depth[s])
        {
            sum--;
            p[e]=find(grand[e]);
            e=p[e];
        }
        else
        {
            sum--;
            sum--;
            p[s]=find(grand[s]);
            p[e]=find(grand[e]);
            s=p[s];
            e=p[e];
        }
    }
}

void init()
{
    sign=0;
    time=top=cnt=0;
    for(int i=0; i<=n; i++)
    {
        rehead[i]=head[i]=-1;
        depth[i]=0;
        p[i]=grand[i]=i;
        dfn[i]=0;
    }
}

int main()
{
    int s,e,u=0;
    while(scanf("%d %d",&n,&m)!=EOF,n+m)
    {
        init();
        for(int i=1; i<=m; i++)
        {
            scanf("%d %d",&s,&e);
            add_edge(s,e);
            add_edge(e,s);
        }
        tanjan(1,-1);
        sign=0;
        for(int i=1; i<=n; i++)
        {
            s=i;
            for(int j=head[i]; j!=-1; j=load[j].p)
            {
                e=load[j].e;
                if(belong[s]!=belong[e])
                    add(belong[s],belong[e]);  ///只需要加一次边,因为前面建立的就是双向边
            }
        }
        dfs(1,-1);
        scanf("%d",&op);
        printf("Case %d:\n",++u);
        sum=cnt-1;
        while(op--)
        {
            scanf("%d %d",&s,&e);
            ///直接从该点的根开始
            s=find(belong[s]);
            e=find(belong[e]);
            lca(s,e);
            printf("%d\n",sum);
        }
        printf("\n");
    }
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值