POJ

来源:互联网 发布:小天才手表没有网络 编辑:程序博客网 时间:2024/06/06 19:47

POJ - 3177

题意+思路:问最少能补成几条边,使这个图成为边双连通。

具体的做法是把每个双连通分量缩成一个点,(求边双连通分量: 先找桥,然后把所有的桥从图中删去,这样图中剩下的几块连起来的子图,每个子图是一个边双连通分量),缩点的时候,把所有的桥加回来,而这题所求的加最少的边来补全成双连通图,ans = (num(leaf) + 1) / 2 ;

原因:缩点后的图,可以看作一棵树,树上的每条边都是需要补充的桥,那么我们肯定是先找树上路径最长的,再找次长的,直到成为一个双连通。树上路径最长的起点和终点肯定是在叶子结点上,所以每次找lca最远的两个叶子结点,把他们连起来,所有偶数个叶子节点的时候,刚好是num(leaf)/2,奇数就是(num(leaf)+1)/2,所以就有了那个式子。

这题的去重我是直接在add()加边的时候操作的,但是这样其实不够快,毕竟每次都要找以u为起点的边。

参考了一下别人的代码,tarjan中会有一个参数fa来判断是否是走过来的那条边,如果用这条边在edge中的下标来传递参数,那么在tarjan中就可以判断一下到底这条边是走过来的那条边,还是重复的边了,如果是重复的边,还是让它去往下去更新,这样就能得到正确的结果。


#include <cstdio>#include <cstring>#include <iostream>#include <algorithm>#include <map>using namespace std;const int maxe = 1e4 + 10, maxn = 5e3 + 50;struct node{    int to,next;    int tag,isb;    //判断重边与是否为桥    node(){}    node(int a,int b,int c,int d) {to = a; next = b, tag = c, isb = d;}}edge[maxe << 2];int h[maxn], dfn[maxn], low[maxn], bid[maxn] , vis[maxn];int in[maxn], isb[maxn];int n,m;int edgenum,tot;void init(){    for(int i = 1; i <= n; i++)        h[i] = -1, low[i] = dfn[i] = bid[i] = vis[i] = 0;    for(int i = 1; i <= n ; i++)        bid[i] = in[i] = isb[i] = 0;    edgenum = tot = 0;}void add(int u,int to){    int flag = -1;       //判重    for(int i = h[u]; ~i; i = edge[i].next)    {        int v = edge[i].to;        if(v == to) {flag = i;break;}    }    if(~flag) {edge[flag].tag = 1; return;}    edge[edgenum] = node(to,h[u],0,0);    h[u] = edgenum++;}int tarjan(int u,int pre)       //找桥标记一下{    dfn[u] = low[u] = ++tot;    for(int i = h[u]; ~i ; i = edge[i].next)    {        int v = edge[i].to;        if(!dfn[v])        {            tarjan(v,u);            low[u] = min(low[u],low[v]);            if(!edge[i].tag && low[v] > dfn[u])            {                edge[i].isb = 1;                edge[i^1].isb = 1;            }        }        else if(dfn[v] < dfn[u] && v != pre)            low[u] = min(low[u],dfn[v]);    }    return 0;}void shrink(int u,int root,int pre){    bid[u] = root;    vis[u] = true;    for(int i = h[u]; ~i; i = edge[i].next)    {        int v = edge[i].to;        if(!edge[i].isb && v != pre && !vis[v])            shrink(v,root,u);    }}int solve(){    for(int i = 1; i <= n ; i++)    for(int j = h[i]; ~j ; j = edge[j].next)    {        int u = i, v = edge[j].to;        if(edge[j].isb == 0) continue;  //如果不是桥        in[bid[u]] ++;    }    int ans = 0;    //for(int i = 1;i <= n ; i++)    //    cout << isb[i] << " " << in[i] << endl;    for(int i = 1; i <= n ; i++)        if(isb[i] && in[i] == 1) ans++;    printf("%d\n",(ans+1)/2);    return 0;}int main(){    //freopen("D://in.txt","r",stdin);    while(~scanf("%d%d",&n,&m))    {        init();        for(int i = 0; i < m ; i++)        {            int a,b;            scanf("%d%d",&a,&b);            add(a,b);   add(b,a);        }        //找桥标记桥        for(int i = 1 ; i <= n ; i++)            if(!dfn[i]) tarjan(i,-1);        /*for(int i = 1; i <= n; i++)            for(int j = h[i]; ~j; j = edge[j].next)        {            int v = edge[j].to;            cout <<"f to isb "<< i << " "  << v << " " << edge[j].isb << endl;        }        */        for(int i = 1 ; i <= n ; i++)            if(!bid[i]) { isb[i] = true; shrink(i,i,-1); }        solve();    }    return 0;}