蓝桥杯 试题 历届试题 危险系数 并查集/dfs

问题描述

抗日战争时期,冀中平原的地道战曾发挥重要作用。

地道的多个站点间有通道连接,形成了庞大的网络。但也有隐患,当敌人发现了某个站点后,其它站点间可能因此会失去联系。

我们来定义一个危险系数DF(x,y):

对于两个站点x和y (x != y), 如果能找到一个站点z,当z被敌人破坏后,x和y不连通,那么我们称z为关于x,y的关键点。相应的,对于任意一对站点x和y,危险系数DF(x,y)就表示为这两点之间的关键点个数。

本题的任务是:已知网络结构,求两站点之间的危险系数。

输入格式

输入数据第一行包含2个整数n(2 <= n <= 1000), m(0 <= m <= 2000),分别代表站点数,通道数;

接下来m行,每行两个整数 u,v (1 <= u, v <= n; u != v)代表一条通道;

最后1行,两个数u,v,代表询问两点之间的危险系数DF(u, v)。

输出格式
一个整数,如果询问的两点不连通则输出-1.
样例输入
7 6
1 3
2 3
3 4
3 5
4 5
5 6
1 6
样例输出
2
解题思路:
  • 题目即求割点。因为并查集可以判断两点是否联通(类似于蓝桥杯发现环:https://www.cnblogs.com/w-like-code/p/12934398.html),所以一个思路是依次去掉通道,判断此时u,v是否联通,

若不联通说明与此边的点为关键点。注意让避免重复计算某点,且要排除u,v。

  • 实现代码:
  1 #include<cstdio>
  2 
  3 const int Max_N = 1000;
  4 const int Max_M = 2000;
  5 
  6 int n,m;
  7 int U[Max_M],V[Max_M]; //保存通道两点 
  8 int u,v; //待查询 
  9 
 10 int par[Max_N+1];
 11 int rank[Max_N+1];
 12 
 13 bool used[Max_N+1];
 14 
 15 void init( int n )
 16 {
 17     for( int i=1; i<=n; i++ )
 18     {
 19         par[i] = i;
 20         rank[i] = 0;
 21     }
 22 }
 23 
 24 int find( int x )
 25 {
 26     if( par[x]==x ){
 27         return x;
 28     }
 29     return par[x] = find( par[x] );
 30 }
 31 
 32 void unite( int x, int y)
 33 {
 34     x = find(x); y = find(y);
 35     if( x==y ){
 36         return;
 37     }
 38     if( rank[x]<rank[y] ){
 39         par[x] = y;
 40     }
 41     else{
 42         par[y] = x;
 43         if( rank[x]==rank[y] ){
 44             rank[x]++;
 45         }
 46     }
 47 }
 48 
 49 bool same( int x, int y )
 50 {
 51     return find(x)==find(y);
 52 }
 53 
 54 void solve()
 55 {
 56     //首先判断u,v是否联通  
 57     init( n );
 58     for( int i=0; i<m; i++ ){
 59         unite( U[i],V[i] );
 60     }
 61     if( !same(u,v) ){  
 62         printf("-1\n");
 63         return;
 64     }
 65     
 66     //每次判处下标为i的通道 用并查集判断关键点 
 67     int res = 0;
 68     for( int i=0; i<m; i++ )
 69     {
 70         init(n);
 71         for( int j=0; j<m; j++ )
 72         {
 73             if( i==j )    continue;
 74             
 75             unite(U[j],V[j]);
 76         }
 77         if( !same(u,v) ){
 78             if( !used[U[i]] ){ //用used[]避免重复计算 
 79                 res++;
 80                 used[U[i]] = true;
 81             }
 82             if( !used[V[i]] ){
 83                 res++;
 84                 used[V[i]] = true;
 85             }
 86         }
 87     }
 88     if( used[u] )    res--;
 89     if( used[v] )    res--;
 90     
 91     printf("%d\n",res);
 92 }
 93 
 94 int main()
 95 {
 96     scanf("%d%d",&n,&m);
 97     for( int i=0; i<m; i++ )
 98     {
 99         scanf("%d%d",&U[i],&V[i]);
100     }
101     scanf("%d%d",&u,&v);
102     
103     solve();
104     
105     return 0;
106 }
View Code
  • 另一个思路是用dfs判断从u到v的所有路径,而关键点就是每个u到v路径都要经过的点。
  • 实现代码: 首先思路是若dfs到达v,返回true,所有返回true的节点都+1。但可能出现:
  • 对于五角星割点,其只有一次返回true,计数为1,但实际上u-v路径数为2

  • 错误代码: 

 1 #include<cstdio>
 2 #include<vector>
 3 using namespace std;
 4 
 5 const int Max_N = 1000;
 6 const int Max_M = 2000;
 7 
 8 //输入 
 9 int n,m;
10 int u,v;
11 vector<int> G[Max_N+1]; //
12 
13 bool visited[Max_N+1]; //visited[i]:在当前dfs()中是否访问过i 
14 int count[Max_N+1]; //count[i]:下标i出现在u-v路径中的次数 
15 int sum; //计算所有路径数 
16  
17 bool dfs( int x )
18 {
19     if( x==v ){
20         sum++;
21         return true;
22     }
23     
24     bool flag = false;
25     for( int i=0; i<G[x].size(); i++ )
26     {
27         int x_ = G[x][i];
28         if( !visited[x_] )
29         {
30             visited[x_] = true;
31             if( dfs( x_ ) ){
32                 count[x]++;
33                 flag = true;
34             }
35             visited[x_] = false;    
36         } 
37     }
38     return flag;
39 }
40 
41 void solve()
42 {    
43     
44     visited[u] = true;
45     if( !dfs( u ) ){//若u,v不连通 
46         printf("-1\n");
47         return;
48     }
49     
50     int res = 0;
51     for( int i=1; i<=n; i++ )
52     {
53         if( count[i]==sum ){
54             res++;
55         }
56     }
57     
58     if( count[u]==sum )    res--;//多记录了u 
59     
60     printf("%d\n",res); 
61 }
62 
63 int main()
64 {
65     scanf("%d%d",&n,&m);
66     for( int i=0; i<m; i++ )
67     {
68         int x,y;
69         scanf("%d%d",&x,&y);
70         G[x].push_back(y); G[y].push_back(x);
71     }
72     scanf("%d%d",&u,&v);
73     
74     solve();
75     
76     return 0;
77 }
View Code
  • 只要用一个数组将经过节点保存,并记录数组长度,当到达v时把数组中节点计数即可。
  • 正确代码:
 1 #include<cstdio>
 2 #include<vector>
 3 using namespace std;
 4 
 5 const int Max_N = 1000;
 6 const int Max_M = 2000;
 7 
 8 //输入 
 9 int n,m;
10 int u,v;
11 vector<int> G[Max_N+1]; //
12 
13 bool visited[Max_N+1]; //visited[i]:在当前dfs()中是否访问过i 
14 int count[Max_N+1]; //count[i]:下标i出现在u-v路径中的次数 
15 int sum; //计算所有路径数 
16 
17 int way[Max_N]; //记录路径节点 
18  
19 void dfs( int x , int index )
20 {
21     if( x==v ){
22         sum++;
23         for( int i=0; i<index; i++ ){
24             count[way[i]]++;
25         }
26         return;
27     }
28     
29     way[index] = x;
30     for( int i=0; i<G[x].size(); i++ )
31     {
32         int x_ = G[x][i];
33         if( !visited[x_] )
34         {
35             visited[x_] = true;
36             dfs( x_, index+1 );
37             visited[x_] = false;    
38         } 
39     }
40 }
41 
42 void solve()
43 {    
44     
45     visited[u] = true;
46     dfs( u , 0 );
47     
48     if( count[u]==0 ){
49         printf("-1\n");
50         return;
51     }
52     
53     int res = 0;
54     for( int i=1; i<=n; i++ )
55     {
56         if( count[i]==sum ){
57             res++;
58         }
59     }
60     
61     printf("%d\n",res-1); //多记录了节点u 
62 }
63 
64 int main()
65 {
66     scanf("%d%d",&n,&m);
67     for( int i=0; i<m; i++ )
68     {
69         int x,y;
70         scanf("%d%d",&x,&y);
71         G[x].push_back(y); G[y].push_back(x);
72     }
73     scanf("%d%d",&u,&v);
74     
75     solve();
76     
77     return 0;
78 }
View Code
posted @ 2020-10-31 15:09  代码改变头发  阅读(119)  评论(0编辑  收藏  举报