1. 程式人生 > >藍橋杯歷屆試題 危險係數(dfs或者並查集求無向圖關於兩點的割點個數)

藍橋杯歷屆試題 危險係數(dfs或者並查集求無向圖關於兩點的割點個數)

Description

抗日戰爭時期,冀中平原的地道戰曾發揮重要作用。

地道的多個站點間有通道連線,形成了龐大的網路。但也有隱患,當敵人發現了某個站點後,其它站點間可能因此會失去聯絡。

我們來定義一個危險係數DF(x,y):

對於兩個站點x和y (x != y), 如果能找到一個站點z,當z被敵人破壞後,x和y不連通,那麼我們稱z為關於x,y的關鍵點。相應的,對於任意一對站點x和y,危險係數DF(x,y)就表示為這兩點之間的關鍵點個數。

本題的任務是:已知網路結構,求兩站點之間的危險係數。

Input

輸入資料第一行包含2個整數n(2 <= n <= 1000), m(0 <= m <= 2000),分別代表站點數,通道數;

接下來m行,每行兩個整數 u,v (1 <= u, v <= n; u != v)代表一條通道;

最後1行,兩個數u,v,代表詢問兩點之間的危險係數DF(u, v)。

Output

一個整數,如果詢問的兩點不連通則輸出-1.

Sample Input

7 6
1 3
2 3
3 4
3 5
4 5
5 6
1 6

Sample Output

2

Source

藍橋杯 解法1:DFS 求關於兩個點的割點
dfs爆搜,找到這兩點間的所有路徑數目ans,如果在1這些路徑中,某些點出現的次數是ans
那麼該點一定是關於目標兩點的割點 具體做法:
搜尋路徑,記錄路徑中每個結點的出現次數,最後與可行路徑數比較 將目標兩點設定為起點s和終點f,dfs爆搜其路徑
邊搜邊記錄其路徑,搜到終點之後,將記錄的路徑的結點出現次數加1
如果在全部搜尋完之後,有點在所有可行路徑中出現的次數等於可行路徑數,那麼該點是關於起點和終點和割點
#include<stdio.h>
#include
<iostream> #include<math.h> #include<string.h> #include<set> #include<map> #include<list> #include<queue> #include<algorithm> using namespace std; typedef long long LL; int mon1[13]= {0,31,28,31,30,31,30,31,31,30,31,30,31}; int mon2[13]= {0,31,29,31
,30,31,30,31,31,30,31,30,31}; int dir[4][2]= {{0,1},{0,-1},{1,0},{-1,0}}; int getval() { int ret(0); char c; while((c=getchar())==' '||c=='\n'||c=='\r'); ret=c-'0'; while((c=getchar())!=' '&&c!='\n'&&c!='\r') ret=ret*10+c-'0'; return ret; } #define max_v 1005 int vis[max_v]; int cnt[max_v]; int path[max_v]; int G[max_v][max_v]; int n,m; int ans; int s,f; void init() { memset(vis,0,sizeof(vis)); memset(cnt,0,sizeof(cnt)); memset(path,0,sizeof(path)); memset(G,0,sizeof(G)); ans=0; } void dfs(int cur,int len) { if(cur==f) { ans++; for(int i=1;i<len;i++) cnt[path[i]]++; return ; } for(int i=1;i<=n;i++) { if(G[cur][i]&&vis[i]==0) { vis[i]=1; path[len]=i; dfs(i,len+1); vis[i]=0; } } } int main() { int x,y; scanf("%d %d",&n,&m); init(); for(int i=1;i<=m;i++) { scanf("%d %d",&x,&y); G[x][y]=G[y][x]=1; } scanf("%d %d",&s,&f); vis[s]=1; path[1]=s; dfs(s,2); if(ans==0) { printf("-1\n"); return 0; } int sum=0; for(int i=1;i<=n;i++) if(cnt[i]==ans) sum++; printf("%d\n",sum-2); return 0; } /* 求關於兩個點的割點 dfs爆搜,找到這兩點間的所有路徑數目ans,如果在1這些路徑中,某些點出現的次數是ans 那麼該點一定是關於目標兩點的割點 具體做法: 搜尋路徑,記錄路徑中每個結點的出現次數,最後與可行路徑數比較 將目標兩點設定為起點s和終點f,dfs爆搜其路徑 邊搜邊記錄其路徑,搜到終點之後,將記錄的路徑的結點出現次數加1 如果在全部搜尋完之後,有點在所有可行路徑中出現的次數等於可行路徑數,那麼該點是關於起點和終點和割點 */

解法二:並查集

無向圖求關於某兩點的割點個數
遍歷每個點,進行多次並查集操作,如果去除和某個點有關的所有邊
導致起點和終點不能連通,那麼該點屬於關於這兩點的割點
該方法屬於暴力法

#include<stdio.h>
#include<iostream>
#include<math.h>
#include<string.h>
#include<set>
#include<map>
#include<list>
#include<queue>
#include<algorithm>
using namespace std;
typedef long long LL;
int mon1[13]= {0,31,28,31,30,31,30,31,31,30,31,30,31};
int mon2[13]= {0,31,29,31,30,31,30,31,31,30,31,30,31};
int dir[4][2]= {{0,1},{0,-1},{1,0},{-1,0}};

int getval()
{
    int ret(0);
    char c;
    while((c=getchar())==' '||c=='\n'||c=='\r');
    ret=c-'0';
    while((c=getchar())!=' '&&c!='\n'&&c!='\r')
        ret=ret*10+c-'0';
    return ret;
}

#define max_v 1005
int pa[max_v];
int n,m;
int a[max_v],b[max_v];
int s,f;
void init()
{
    for(int i=1;i<=n;i++)
        pa[i]=i;
}
int find_set(int x)
{
    if(x!=pa[x])
        pa[x]=find_set(pa[x]);
    return pa[x];
}
void union_set(int x,int y)
{
    x=find_set(x);
    y=find_set(y);
    if(x==y)
        return ;
    pa[x]=y;
}
int fun(int v)
{
    init();
    for(int i=1;i<=m;i++)
    {
        if(a[i]==v||b[i]==v)
            continue;
        union_set(a[i],b[i]);
    }
    if(find_set(s)!=find_set(f))
        return 0;
    else
        return 1;
}
int main()
{
    int x,y;
    scanf("%d %d",&n,&m);
    for(int i=1;i<=m;i++)
    {
        scanf("%d %d",&x,&y);
        a[i]=x;
        b[i]=y;
    }
    scanf("%d %d",&s,&f);
    int sum=0;
    for(int i=1;i<=n;i++)
    {
        if(i==s||i==f)
            continue;
       if(fun(i)==0)
        sum++;
    }
    printf("%d\n",sum);
    return 0;
}
/*
無向圖求關於某兩點的割點個數
遍歷每個點,進行多次並查集操作,如果去除和某個點有關的所有邊
導致起點和終點不能連通,那麼該點屬於關於這兩點的割點
該方法屬於暴力法
*/