poj 3694Network 双联通分量+lca

输入:
3 2
1 2
2 3
2
1 2
1 3
题意就是先输入n,m 表示一颗树有n个节点,m条无向边。然后又q次操作, 每一次操作 输入i,j 表示在节点i和节点j之间加一条边,并且询问在加边之后的图中存在多少个桥。

在这里先感谢大牛:
http://www.cnblogs.com/scau20110726/archive/2013/06/14/3135095.html
他的博客真的给了我很多的帮助,在我犯了很多很多错误的时候 用很简短很清晰的代码拨正了我的错误思维。膜一发

这题 我一开始是这样写的: 先dfs 跑一发tarjan,把dfn,low 桥 连通块 都跑出来。 然后再 dfs 跑一发st的lca在线询问, 然后对于加边 i,j 我们就从i 向上遍历到 lca,从j 向上遍历到lca,扫描减少的桥的数量。 不知道是不是姿势写的太丑,还是两遍dfs耗时间,反正就是T了。 (现在想想 好像是因为 fa[i] 写的有问题才会T)
然后膜了一下别人的代码, 感觉至少比我的代码少 100 行 TUT,我好菜啊

感觉自己没有学懂tarjan啊,好菜啊,明明一遍dfs就可以做完,因为我们后面lca 用很朴素的想法的话用dfn和low就可以完成了,话不多说 上代码:

#include <cstdio>
#include <cmath>
#include <cstring>
#include <ctime>
#include <iostream>
#include <algorithm>
#include <set>
#include <vector>
#include <sstream>
#include <queue>
#include <typeinfo>
#include <fstream>
#include <map>
#include <stack>
typedef long long ll;
using namespace std;
const int N = 100010;//点数
const int MAXM = 200010;//边数,因为是无向图,所以这个值要*2

int head[N],dfn[N],low[N],vis[N],fa[N],dcnt,bridge;
int isbri[N];
struct Edge{
    int u,v,next;
    int used;  //判断是否为桥
    int bri;
}edge[2*MAXM];
int tot;
void addedge(int u,int v){
    edge[tot].v=v;
    edge[tot].used=0;
    edge[tot].next=head[u];
    head[u]=tot++;
}
void dfs(int u,int pre){
//  printf("u=%d\n",u);
    vis[u]=1;
    dfn[u]=low[u]=++dcnt;
    for(int i=head[u];i!=-1;i=edge[i].next){
        int v=edge[i].v;
//      if(v==pre) continue;
        if(!edge[i].used){
            edge[i].used=edge[i^1].used=1;
            if(!vis[v]){
                fa[v]=u;
                dfs(v,u);
                if( low[u] > low[v] )  low[u] = low[v];
                if(low[v] > dfn[u]){   //桥
                    bridge++;
                    isbri[v]=1;  // 精妙: 只会设置每一条路的终点,反向并不会设置
                }
            }
            else if(vis[u] && low[u]> dfn[v])
                low[u]=dfn[v];
        }
    }
}
void LCA(int u,int v){ //重点来了, 其实LCA完全没必要再扫一遍,借助low 和 dfn 完全够实现lca

    if(dfn[u] < dfn[v]) swap(u,v);  // dfn  即 深度
    while(dfn[u] > dfn[v]){   //因为 u是较深的那个点,先尽量往上移动
        if(isbri[u]) bridge--;
        isbri[u]=0;
        u=fa[u];
    }
    while(dfn[v]>dfn[u]){
        if(isbri[v]) bridge--;
        isbri[v]=0;
        v=fa[v];
    }
    while(u!=v){
        if(isbri[u]) bridge--;
        if(isbri[v]) bridge--;
        isbri[u]=isbri[v]=0;
        u=fa[u];
        v=fa[v];
    }
    //最后 u=v , 这个u就是lca,但我不说
    //讲道理  复杂度有点高啊
}
bool flag[N];
void init(){
    tot=0;
    memset(vis,0,sizeof(vis));
    memset(head,-1,sizeof(head));
    memset(isbri,0,sizeof(isbri));
    memset(flag,0,sizeof(flag));
    dcnt = bridge = 0;
}
int main(){
   // freopen("1.txt","r",stdin);
    int n,m,cas=0;
    while(~scanf("%d %d",&n,&m) && (n||m)){
        init();
        printf("Case %d:\n",++cas);
        for(int i=1;i<=m;i++){
            int u,v;
            scanf("%d %d",&u,&v);
            addedge(u,v);
            addedge(v,u);
            flag[v]=true;
        }
        dfs(1,1);
        int q;
//      printf("bri=%d\n",bridge);
//      for(int i=1;i<=n;i++){
//          printf("%d ",dfn[i]);
//      }
//        cout<<endl;
        scanf("%d",&q);
        while(q--){
            int a,b;
            scanf("%d %d",&a,&b);
            if(bridge==0){
                printf("0\n");
                continue;
            }
            LCA(a,b);
            printf("%d\n",bridge);
        }
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值