HDU4738 Caocao's Bridges (求桥)

来源:互联网 发布:孔子学院的数据 编辑:程序博客网 时间:2024/05/17 15:59


题意

       现在有个(可重边)无向图,无向图的每条边上都有一定数目的守卫,你现在想派人去炸掉这个图的一条边,是的该图不连通。但是你只能炸1条边且如果该边守卫为x人,那么你至少要派x个人过去。所以现在问你最少需要派多少人出发?

思路:就是求一个有重边的无向图的桥,有几个比较坑的地方

            1,所给的图可能不连通,且不连通的时候不需要炸,输出0

            2,当所要去炸的桥上的守卫数=0时,需要输出1而不是0

            3,有重边

            我的思路很简单,如果有重边的话那么就一定不是桥,那么只需要给一个特别一点值,在dfs里面更新的时候判断如果是这个特别的值就可以跳过了。


这个不能像求联通块那样 直接 !k continue就好了, 因为求割边是 在!dfn时候做的。。含重边求桥两种方法把

#include <iostream>#include <cstring>#include <cstdio>#include <vector>#include <stack>#include <map>#include <algorithm>using namespace std;const int maxn = 1e3 + 5;const int INF = 1e9;int n, m,low[maxn], dfn[maxn], id[maxn], scc_cnt, dfs_cnt, cnt;int g[maxn][maxn], ans, d[maxn][maxn];vector<int> v[maxn];map<int, int> mp;void init(){    memset(low, 0, sizeof(low));    memset(id, 0, sizeof(id));    memset(dfn, 0, sizeof(dfn));    scc_cnt = dfs_cnt = cnt = 0;    for(int i = 0; i < maxn; i++)        v[i].clear();    memset(g, 0, sizeof(g));}void tarjan(int x, int f){    dfn[x] = low[x] = ++dfs_cnt;    int k = 0;    for(int i = 0; i < v[x].size(); i++)    {        int to = v[x][i];        if(to == f && !k)        {            k++;            continue;        }        if(!dfn[to])        {            tarjan(to, x);            low[x] = min(low[x], low[to]);            if(low[to] > dfn[x] && g[x][to] != -1)                ans = min(ans, g[x][to]), cnt++;        }        else if(dfn[to] < dfn[x])            low[x] = min(low[x], dfn[to]);    }}int main(){//    freopen("in.txt", "r", stdin);    while(~scanf("%d%d",&n, &m), n+m)    {        init();        int x, y, z;        for(int i = 1; i <= m; i++)        {            scanf("%d%d%d", &x, &y, &z);            if(!g[x][y])            {                v[x].push_back(y);                v[y].push_back(x);                g[x][y] = z;                g[y][x] = z;            }            else            {                g[x][y] = -1;                g[y][x] = -1;            }        }        ans = INF;        cnt = 0;        tarjan(1, -1);//        cout << cnt << endl;        int flag = 0;        for(int i = 1; i <= n; i++)            if(!dfn[i])            {                flag = 1;                break;            }        if(ans == 0)            puts("1");        else if(flag)            puts("0");        else if(ans == INF)            puts("-1");        else            printf("%d\n", ans);    }    return 0;}

    本题的本质还是无向图求桥,且求得是守卫数目最少的那个桥。但是本题有3个点要注意:

       1.所给的图可能不连通,且不连通的时候不需要炸,输出0.

       2.当所要去炸的桥上的守卫数=0时,我们需要派的人数是1不是0.

       3.任意两个节点u与v之间可能存在多条边。

       对于上面的1与2点,我们在原始tarjan()函数运行完后加一些判断就能解决.

       不过对于重边无向图,首先我们要用邻接表来保存图了(不能再用vector的邻接矩阵了).

       然后之前无重边的时候我们都是用过fa来标记父节点的,如果u的儿子等于fa,那么直接跳过。即如果u不通过儿子连回fa的话,low[u]==pre[u]肯定>pre[fa]。现在本题其实u是可以通过另一条(fa,u)的边连回fa的,所以这里即使u不通过儿子连回fa的话,low[u]==也可以==pre[fa]。因为fa通过边1到u,u可以通过边2到fa。

       所以本题把无向图转换成有向图来做:

       把每条无向边分为两条有向边i与i+1,如果u通过边i到达了v,那么v中必然有一条边是i^1且可以通过该i^1边到u.所以如果在v节点遍历时到达i^1边时,我们直接跳过.

       具体实现还是需要体会代码才能清晰.



#include<cstdio>#include<cstring>#include<algorithm>using namespace std;const int maxn=1000+10;const int maxm=2*1000*1000+100;int n,m;int tot;int head[maxn];struct Edge{    int to,next,w;}edges[maxm];void add_edge(int u,int v,int w){    edges[tot]=(Edge){v,head[u],w};    head[u]=tot++;    edges[tot]=(Edge){u,head[v],w};    head[v]=tot++;}int pre[maxn],low[maxn];int dfs_clock,point_num;int ans;void tarjan(int u,int E){    low[u]=pre[u]=++dfs_clock;    for(int e=head[u];e!=-1;e=edges[e].next)    {        int v=edges[e].to;        if(e==(E^1)) continue;        if(!pre[v])        {            tarjan(v,e);            low[u]=min(low[u],low[v]);            if(low[v]>pre[u])                ans=min(ans,edges[e].w);        }        else low[u]=min(low[u],pre[v]);    }    point_num++;}int main(){    while(scanf("%d%d",&n,&m)==2&&n)    {        ans=1000000;        dfs_clock=point_num=tot=0;        memset(pre,0,sizeof(pre));        memset(head,-1,sizeof(head));        for(int i=0;i<m;i++)        {            int u,v,w;            scanf("%d%d%d",&u,&v,&w);            add_edge(u,v,w);        }        tarjan(1,-1);        if(point_num<n) printf("0\n");          //图不连通,不用炸        else if(ans==1000000) printf("-1\n");   //图中无桥        else if(ans==0) printf("%d\n",1);       //桥上兵为0        else printf("%d\n",ans);    }    return 0;}
kuangbin板子

/* ***********************************************Author        :kuangbinCreated Time  :2013/9/15 星期日 12:11:49File Name     :2013杭州网络赛\1001.cpp************************************************ */#pragma comment(linker, "/STACK:1024000000,1024000000")#include <stdio.h>#include <string.h>#include <iostream>#include <algorithm>#include <vector>#include <queue>#include <set>#include <map>#include <string>#include <math.h>#include <stdlib.h>#include <time.h>using namespace std;const int INF = 0x3f3f3f3f;/**  求 无向图的割点和桥*  可以找出割点和桥,求删掉每个点后增加的连通块。*  需要注意重边的处理,可以先用矩阵存,再转邻接表,或者进行判重*/const int MAXN = 10010;const int MAXM = 2000010;struct Edge{    int to,next;    int w;    bool cut;//是否为桥的标记}edge[MAXM];int head[MAXN],tot;int Low[MAXN],DFN[MAXN],Stack[MAXN];int Index,top;bool Instack[MAXN];bool cut[MAXN];int add_block[MAXN];//删除一个点后增加的连通块int bridge;void addedge(int u,int v,int w){    edge[tot].to = v;edge[tot].next = head[u];edge[tot].cut = false;    edge[tot].w = w;    head[u] = tot++;}void Tarjan(int u,int pre){    int v;    Low[u] = DFN[u] = ++Index;    Stack[top++] = u;    Instack[u] = true;    int son = 0;    int pre_num = 0;    for(int i = head[u];i != -1;i = edge[i].next)    {        v = edge[i].to;        if(v == pre && pre_num == 0)        {            pre_num++;            continue;        }        if( !DFN[v] )        {            son++;            Tarjan(v,u);            if(Low[u] > Low[v])Low[u] = Low[v];            //桥            //一条无向边(u,v)是桥,当且仅当(u,v)为树枝边,且满足DFS(u)<Low(v)。            if(Low[v] > DFN[u])            {                bridge++;                edge[i].cut = true;                edge[i^1].cut = true;            }            //割点            //一个顶点u是割点,当且仅当满足(1)或(2) (1) u为树根,且u有多于一个子树。            //(2) u不为树根,且满足存在(u,v)为树枝边(或称父子边,            //即u为v在搜索树中的父亲),使得DFS(u)<=Low(v)            if(u != pre && Low[v] >= DFN[u])//不是树根            {                cut[u] = true;                add_block[u]++;            }        }        else if( Low[u] > DFN[v])             Low[u] = DFN[v];    }    //树根,分支数大于1    if(u == pre && son > 1)cut[u] = true;    if(u == pre)add_block[u] = son - 1;    Instack[u] = false;    top--;}int  solve(int N){    memset(DFN,0,sizeof(DFN));    memset(Instack,false,sizeof(Instack));    memset(add_block,0,sizeof(add_block));    memset(cut,false,sizeof(cut));    Index = top = 0;    bridge = 0;    for(int i = 1;i <= N;i++)        if( !DFN[i] )            Tarjan(i,i);    int ret = INF;    for(int u = 1; u <= N;u++)        for(int i = head[u]; i != -1;i = edge[i].next)            if(edge[i].cut)                ret = min(ret,edge[i].w);    if(ret == INF)ret = -1;    if(ret == 0)ret++;    return ret;}int F[MAXN];int find(int x){    if(F[x] == -1)return x;    else return F[x] = find(F[x]);}void init(){    memset(F,-1,sizeof(F));    tot = 0;    memset(head,-1,sizeof(head));}void bing(int u,int v){    int t1 = find(u);    int t2 = find(v);    if(t1 != t2)F[t1] = t2;}int main(){    //freopen("in.txt","r",stdin);    //freopen("out.txt","w",stdout);    int n,m;    while(scanf("%d%d",&n,&m) == 2)    {        if(n == 0 && m == 0)break;        int u,v,w;        init();        while(m--)        {            scanf("%d%d%d",&u,&v,&w);            if(u == v)continue;            addedge(u,v,w);            addedge(v,u,w);            bing(u,v);        }        bool flag = true;        for(int i = 1; i <= n;i++)            if(find(i) != find(1))                flag = false;        if(!flag)        {            printf("0\n");            continue;        }        printf("%d\n",solve(n));    }    return 0;}




原创粉丝点击