强连通入门练习题

刚入门连通时就是根据这些题来学习新的知识点,刚开始挺自闭的。    :)

题目地址:https://vjudge.net/contest/311227#overview

 


 

POJ-1236 Network of Schools(求连通分量及它们的出度和入度)

 http://poj.org/problem?id=1236

Description

A number of schools are connected to a computer network. Agreements have been developed among those schools: each school maintains a list of schools to which it distributes software (the “receiving schools”). Note that if B is in the distribution list of school A, then A does not necessarily appear in the list of school B 
You are to write a program that computes the minimal number of schools that must receive a copy of the new software in order for the software to reach all schools in the network according to the agreement (Subtask A). As a further task, we want to ensure that by sending the copy of new software to an arbitrary school, this software will reach all schools in the network. To achieve this goal we may have to extend the lists of receivers by new members. Compute the minimal number of extensions that have to be made so that whatever school we send the new software to, it will reach all other schools (Subtask B). One extension means introducing one new member into the list of receivers of one school. 

Input

The first line contains an integer N: the number of schools in the network (2 <= N <= 100). The schools are identified by the first N positive integers. Each of the next N lines describes a list of receivers. The line i+1 contains the identifiers of the receivers of school i. Each list ends with a 0. An empty list contains a 0 alone in the line.

Output

Your program should write two lines to the standard output. The first line should contain one positive integer: the solution of subtask A. The second line should contain the solution of subtask B.

Sample Input

5
2 4 3 0
4 5 0
0
0
1 0

Sample Output

1
2

题目大意:

给定一个有向图,求:

  1. 至少要选几个顶点,才能做到从这些顶点出发,可以到达全部顶点
  2. 至少要加多少条边,才能使得从任何一个顶点出发,都能到达全部顶点

有用的定理:

    有向无环图中所有入度不为0的点,一定可以由某个入度为0的点出发可达。 (由于无环,所以从任何入度不为0的点往回走,必然终止于一个入度为0的点)

解题思路 :

我们可以先进行缩点求出dag图,然后我们考虑第一个问题,求最少发几套软件可以全覆盖,首先题意已经保证了是联通的。然后我们可以想,如果我们把所有没有入边的点都放上软件,是一定可行的。有入边的一定会通过一些边最终从一定有出边的发放软件的地方获得软件。

然后我们考虑第二个问题。这是一个连通图。如果我们有些点没有入点,有些点没出点。那我们如果想办法将入点和一些出点相连,就能保证最后会成为很多圆相连。这样子答案就是没有入边的点和没有出边的点的最大值。

具体来说:

  1. 求出所有强连通分量
  2. 每个强连通分量缩成一点,则形成一个有向无环图DAG。
  3. DAG上面有多少个入度为0的顶点,问题1的答案就是多少

在DAG上要加几条边,才能使得DAG变成强连通的,问题2的答案就是多少

加边的方法: 要为每个入度为0的点添加入边,为每个出度为0的点添加出边 ,假定有 n 个入度为0的点,m个出度为0的点, max(m,n)就是第二个问题的解(证明难,略)

 

 代码如下:

  1 #include <stdio.h>
  2 #include <iostream>
  3 #include <string.h>
  4 #include <algorithm>
  5 #include <stack>
  6 #include <string>
  7 #include <sstream>
  8 using namespace std;
  9 #define maxn 105
 10 
 11 struct Edge{
 12     int v;
 13     int next;
 14 }Edge[maxn*maxn];//边的集合,边最多为n*(n-1),故要开点数的平方
 15 
 16 int node[maxn];//顶点集合
 17 int instack[maxn];//标记是否在stack中
 18 int Belong[maxn];//各顶点属于哪个强连通分量
 19 int DFN[maxn];//节点u搜索的序号(时间戳)
 20 int LOW[maxn];//u或u的子树能够追溯到的最早的栈中节点的序号(时间戳)
 21 int n;//n:点的个数
 22 int cnt_edge;//边的计数器
 23 int Index;//序号(时间戳)
 24 int Bcnt; //有多少个强连通分量
 25 int out[maxn];//存储出度 
 26 int in[maxn];//存储入度 
 27 stack<int> sk;
 28 
 29 void add_edge(int u,int v)//邻接表存储
 30 {
 31     Edge[cnt_edge].next=node[u];
 32     Edge[cnt_edge].v=v;
 33     node[u]=cnt_edge++;
 34 }
 35 
 36 void tarjan(int u)
 37 {
 38     DFN[u]=LOW[u]=++Index;
 39     instack[u]=1;
 40     sk.push(u);
 41     for(int i=node[u];i!=-1;i=Edge[i].next)
 42     {
 43         int v=Edge[i].v;
 44         if(!DFN[v])//如果点v没被访问
 45         {
 46             tarjan(v);
 47             LOW[u]=min(LOW[u],LOW[v]);
 48         }
 49         else //如果点v已经被访问过
 50         {
 51             if(instack[v])
 52                 LOW[u]=min(LOW[u],DFN[v]);
 53         }    
 54     }
 55     if(DFN[u]==LOW[u])
 56     {
 57         Bcnt++;
 58         int t;
 59         do{
 60             t=sk.top();
 61             sk.pop();
 62             instack[t]=0;
 63             Belong[t]=Bcnt;
 64         }while(t!=u);
 65     }
 66 }
 67 
 68 void work()
 69 {
 70     for(int i=1;i<=n;i++)
 71     {
 72         for(int j=node[i];j!=-1;j=Edge[j].next)
 73         {
 74             int v=Edge[j].v;
 75             if(Belong[i]!=Belong[v])
 76             {
 77                 out[Belong[i]]++;
 78                 in[Belong[v]]++;
 79             }
 80         }
 81     }
 82     int RU=0;  //入度为零的分量的个数
 83     int CHU=0;//出度为零的分量的个数
 84     for(int i=1;i<=Bcnt;i++)
 85     {
 86         if(!in[i]) RU++;
 87         if(!out[i]) CHU++;
 88     }
 89     if(Bcnt==1)
 90         printf("1\n0\n");
 91     else
 92         printf("%d\n%d\n",RU,max(RU,CHU));
 93 }
 94 
 95 int main()
 96 {
 97     //freopen("sample.txt","r",stdin);
 98     memset(node,-1,sizeof(node));
 99     scanf("%d",&n);
100     getchar();
101     for(int i=1;i<=n;i++)
102     {
103         int v;
104         string str;
105         getline(cin,str);
106         istringstream ss(str);
107         while(ss >> v&&v)
108         {
109             add_edge(i,v);
110         }
111     }
112     for(int i=1;i<=n;i++)
113     {
114         if(!DFN[i])
115         {
116             tarjan(i);
117         }
118     }
119     work();
120     return  0;
121 }

 

 

补充:

POJ-2186 Popular Cows 

http://poj.org/problem?id=2186

Description

Every cow's dream is to become the most popular cow in the herd. In a herd of N (1 <= N <= 10,000) cows, you are given up to M (1 <= M <= 50,000) ordered pairs of the form (A, B) that tell you that cow A thinks that cow B is popular. Since popularity is transitive, if A thinks B is popular and B thinks C is popular, then A will also think that C is 
popular, even if this is not explicitly specified by an ordered pair in the input. Your task is to compute the number of cows that are considered popular by every other cow. 

Input

* Line 1: Two space-separated integers, N and M 
* Lines 2..1+M: Two space-separated numbers A and B, meaning that A thinks B is popular. 

Output

* Line 1: A single integer that is the number of cows who are considered popular by every other cow. 

Sample Input

3 3
1 2
2 1
2 3

Sample Output

 1

Hint

Cow 3 is the only cow of high popularity. 

 

题意:

给定一个有向图,求有多少个顶点是由任何顶点出发都可达的。 
有向无环图中唯一出度为0的点,一定可 以由任何点出发均可达(由于无环,所以从任何点出发往前走,必然终止于 一个出度为0的点) 
解题思路 :
1. 求出所有强连通分量 
2. 每个强连通分量缩成一点,则形成一个有 向无环图DAG。 
3. DAG上面如果有唯一的出度为0的点,则该点 能被所有的点可达。那么该点所代表的连通分 量上的所有的原图中的点,都能被原图中的所 有点可达,则该连通分量的点数,就是答案。 
4. DAG上面如果有不止一个出度为0的点,则 这些点互相不可达,原问题无解,答案为0 

缩点的时候不一定要构造新图,只要把不同强连通分量的点染不同颜色,然后考察各种颜色的点有没有连到别的颜色的边即可(即其对应的缩点后的DAG图上的点是否有出边)。

 

代码:

不贴了,和上一题差不多

 

 


 

 

POJ-1144 Network(求割点)

http://poj.org/problem?id=1144

 

题目描述

A Telephone Line Company (TLC) is establishing a new telephone cable network. They are connecting several places numbered by integers from 1 to N. No two places have the same number. The lines are bidirectional and always connect together two places and in each place the lines end in a telephone exchange. There is one telephone exchange in each place. From each place it is possible to reach through lines every other place, however it need not be a direct connection, it can go through several exchanges. From time to time the power supply fails at a place and then the exchange does not operate. The officials from TLC realized that in such a case it can happen that besides the fact that the place with the failure is unreachable, this can also cause that some other places cannot connect to each other. In such a case we will say the place (where the failure occured) is critical. Now the officials are trying to write a program for finding the number of all such critical places. Help them

Input

The input file consists of several blocks of lines. Each block describes one network. In the first line of each block there is the number of places N < 100. Each of the next at most N lines contains the number of a place followed by the numbers of some places to which there is a direct line from this place. These at most N lines completely describe the network, i.e., each direct connection of two places in the network is contained at least in one row. All numbers in one line are separated by one space. Each block ends with a line containing just ‘0’. The last block has only one line with N = 0.

Output

The output contains for each block except the last in the input file one line containing the number of critical places.

Sample Input

复制代码
5
5 1 2 3 4
0
6
2 1 3
5 4 6 2
0
0
复制代码

Sample Output

1
2

题意翻译:

多组数据读入一个数n,n=0时退出,代表n个点的无向图。下面若干行,每行第一个数为u,u=0时退出,后面若干个数v,代表和u相连的点v,遇到换行退出。对于每组数据,输出图的割顶个数(输入较坑,所以详细说明)。 

解题思路:

无向图求割点,标准模版,注意输入字符串的处理。

代码如下:

  1 #include <stdio.h>
  2 #include <iostream>
  3 #include <string.h>
  4 #include <algorithm>
  5 #include <string>
  6 #include <sstream>
  7 using namespace std;
  8 #define maxn 105
  9 
 10 struct edge{
 11     int v;
 12     int next;
 13 }Edge[maxn*maxn];//边的集合,边最多为n*(n-1),故要开点数的平方
 14 
 15 int node[maxn];//顶点集合
 16 int DFN[maxn];//节点u搜索的序号(时间戳)
 17 int LOW[maxn];//u或u的子树能够追溯到的最早的栈中节点的序号(时间戳)
 18 int n;//n:点的个数
 19 int cnt_edge;//边的计数器
 20 int Index;//序号(时间戳)
 21 int fa[maxn]; //父亲编号 
 22 int GD[maxn]; //是否为割点 
 23 
 24 
 25 void add_edge(int u,int v)//邻接表存储
 26 {
 27     Edge[cnt_edge].next=node[u];
 28     Edge[cnt_edge].v=v;
 29     node[u]=cnt_edge++;
 30 }
 31 
 32 void tarjan(int u)
 33 {
 34     int children=0;//记录节点u的子树数 
 35     DFN[u]=LOW[u]=++Index;
 36     for(int i=node[u];i!=-1;i=Edge[i].next)
 37     {
 38         int v=Edge[i].v;
 39         if(!DFN[v])//如果点v没被访问 ,则(u,v)为树边 
 40         {
 41             children++;
 42             fa[v]=u; 
 43             tarjan(v);
 44             LOW[u]=min(LOW[u],LOW[v]);
 45             if(fa[u]==0&&children>1&&!GD[u])
 46             {
 47                 GD[u]=1;
 48             }
 49             if(fa[u]&&LOW[v]>=DFN[u]&&!GD[u])
 50             {
 51                 GD[u]=1;
 52             }
 53                 
 54         }
 55         else //如果点v已经被访问过
 56         {
 57             if(DFN[v])
 58                 LOW[u]=min(LOW[u],DFN[v]);
 59         }
 60     }
 61 }
 62 
 63 void init()//初始化,注意不要把n初始为0 
 64 {
 65     cnt_edge=0;
 66     Index=0;
 67     memset(Edge,0,sizeof(Edge));
 68     memset(node,-1,sizeof(node));
 69     memset(DFN,0,sizeof(DFN));
 70     memset(LOW,0,sizeof(LOW));
 71     memset(fa,0,sizeof(fa));
 72     memset(GD,0,sizeof(GD));
 73 }
 74 
 75 int main()
 76 {
 77     freopen("sample.txt","r",stdin);
 78     while(~scanf("%d",&n)&&n)
 79     {
 80         init();
 81         char str[1010];
 82         getchar();
 83         while(cin.getline(str,1010)&&str[0]!='0')//gets不支持,用cin.getline来代替gets 
 84         {
 85             istringstream is(str);
 86             int u,v;
 87             is>>u;
 88             while(is>>v)//while中不要写成is,要不最后一个信息会重复赋给v两次 
 89             {
 90                 add_edge(u,v);//注意是无向边 
 91                 add_edge(v,u);
 92             }
 93         }
 94         for(int i=1;i<=n;i++)
 95         {
 96             if(!DFN[i])
 97             {
 98                 tarjan(i);
 99             }
100         }
101         int sum=0;
102         for(int i=1;i<=n;i++)
103         {
104             if(GD[i])
105                 sum++;
106         }
107         printf("%d\n",sum);
108     }
109     return  0;
110 }

 


 

 

UVA-796 Critical Links —— 割边(桥)

https://vjudge.net/problem/UVA-796

题目描述

In a computer network a link L, which interconnects two servers, is considered critical if there are at least two servers A and B such that all network interconnection paths between A and B pass through L. Removing a critical link generates two disjoint sub–networks such that any two servers of a sub–network are interconnected. For example, the network shown in figure 1 has three critical links that are marked bold: 0 -1, 3 - 4 and 6 - 7.

It is known that:

1. the connection links are bi–directional;

2. a server is not directly connected to itself;

3. two servers are interconnected if they are directly connected or if they are interconnected with the same server;

4. the network can have stand–alone sub–networks. Write a program that finds all critical links of a given computer network.

Input

The program reads sets of data from a text file. Each data set specifies the structure of a network and has the format: no of servers server0 (no of direct connections) connected server . . . connected server . . . serverno of servers (no of direct connections) connected server . . . connected server The first line contains a positive integer no of servers(possibly 0) which is the number of network servers. The next no of servers lines, one for each server in the network, are randomly ordered and show the way servers are connected. The line corresponding to serverk, 0 ≤ k ≤ no of servers − 1, specifies the number of direct connections of serverk and the servers which are directly connected to serverk. Servers are represented by integers from 0 to no of servers − 1.Input data are correct. The first data set from sample input below corresponds to the network in figure 1, while the second data set specifies an empty network.

Output

The result of the program is on standard output. For each data set the program prints the number of critical links and the critical links, one link per line, starting from the beginning of the line, as shown in the sample output below. The links are listed in ascending order according to their first element. The output for the data set is followed by an empty line.

Sample Input

复制代码
8
0 (1) 1
1 (3) 2 0 3
2 (2) 1 3
3 (3) 1 2 4
4 (1) 3
7 (1) 6
6 (1) 7
5 (0)
0
复制代码

Sample Output

复制代码
3 critical links
0 - 1
3 - 4
6 - 7

0 critical links

复制代码

 

题目大意:给你n个点,m条边,问你有多少桥,并将其输出。 

思路:模板题,不过这一题有格式要求,要求从小到大输出,并且每个数据后有空行。

问题和坑点:

这题一直wa,卡了我几个小时,看了好几个题解,感觉有的还没我写的好,但就是过不了,自闭了。

这题的输入输出样例应该有点问题,我把while中的 if(n==0) break;删了就能过了,但是删了就和样例输出不同了。。。。但就是AC了????

代码如下:

  1 #include <stdio.h>
  2 #include <iostream>
  3 #include <string.h>
  4 #include <algorithm>
  5 #include <string>
  6 #include <set>
  7 #include <sstream>
  8 using namespace std;
  9 #define maxn 1010
 10 
 11 struct edge{
 12     int v;
 13     int next;
 14 }Edge[maxn*maxn];//边的集合
 15 
 16 int node[maxn];//顶点集合
 17 int DFN[maxn];//节点u搜索的序号(时间戳)
 18 int LOW[maxn];//u或u的子树能够追溯到的最早的栈中节点的序号(时间戳)
 19 int n;//n:点的个数
 20 int cnt_edge;//边的计数器
 21 int Index;//序号(时间戳)
 22 int fa[maxn]; //父亲编号 
 23 set<pair<int,int> > st;
 24 set<pair<int,int> >::iterator it;
 25 
 26 void add_edge(int u,int v)//邻接表存储
 27 {
 28     Edge[cnt_edge].next=node[u];
 29     Edge[cnt_edge].v=v;
 30     node[u]=cnt_edge++;
 31 }
 32 
 33 void tarjan(int u)
 34 {
 35     DFN[u]=LOW[u]=++Index;
 36     for(int i=node[u];i!=-1;i=Edge[i].next)
 37     {
 38         int v=Edge[i].v;
 39         if(!DFN[v])//如果点v没被访问 ,则(u,v)为树边 
 40         {
 41             fa[v]=u; 
 42             tarjan(v);
 43             LOW[u]=min(LOW[u],LOW[v]);
 44             if(LOW[v]>DFN[u]) //判断是否为桥
 45             {
 46                 st.insert(make_pair(min(u,v),max(u,v)));
 47             }
 48                 
 49         }
 50         else if(v!=fa[u]) //如果点v已经被访问过
 51         {
 52             if(DFN[v])
 53                 LOW[u]=min(LOW[u],DFN[v]);
 54         }
 55     }
 56 }
 57 
 58 void init()//初始化,注意不要把n初始为0 
 59 {
 60     cnt_edge=0;
 61     Index=0;
 62     memset(Edge,0,sizeof(Edge));
 63     memset(node,-1,sizeof(node));
 64     memset(DFN,0,sizeof(DFN));
 65     memset(LOW,0,sizeof(LOW));
 66     memset(fa,0,sizeof(fa));
 67     st.clear();
 68 }
 69 
 70 int main()
 71 {
 72     freopen("sample.txt","r",stdin);
 73     while(~scanf("%d",&n))
 74     {
 75         init();
 76         int u,m;
 77         for(int i=0;i<n;i++)
 78         {
 79             scanf("%d (%d)",&u,&m);
 80             for(int j=0;j<m;j++)
 81             {
 82                 int v;
 83                 scanf("%d",&v);
 84                 if(v<=u)
 85                     continue;
 86                 add_edge(u,v);
 87                 add_edge(v,u);
 88             }
 89         }
 90         for(int i=0;i<n;i++)
 91         {
 92             if(!DFN[i])
 93             {
 94                 tarjan(i);
 95             }
 96         }
 97         printf("%d critical links\n",st.size());
 98         for(it=st.begin();it!=st.end();it++)
 99         {
100             printf("%d - %d\n",it->first,it->second);
101         }
102         printf("\n");
103 //        if(n==0)              //删了就能过 
104 //            break;
105     }
106     return  0;
107 }

 


 

 

POJ-3694 Network(tarjan求桥,LCA)

https://vjudge.net/problem/POJ-3694

problem

A network administrator manages a large network. The network consists of N computers and M links between pairs of computers. Any pair of computers are connected directly or indirectly by successive links, so data can be transformed between any two computers. The administrator finds that some links are vital to the network, because failure of any one of them can cause that data can't be transformed between some computers. He call such a link a bridge. He is planning to add some new links one by one to eliminate all bridges.

You are to help the administrator by reporting the number of bridges in the network after each new link is added.

Input

The input consists of multiple test cases. Each test case starts with a line containing two integers N(1 ≤ N ≤ 100,000) and M(N - 1 ≤ M ≤ 200,000).
Each of the following M lines contains two integers A and B ( 1≤ A ≠ B ≤ N), which indicates a link between computer A and B. Computers are numbered from 1 to N. It is guaranteed that any two computers are connected in the initial network.
The next line contains a single integer Q ( 1 ≤ Q ≤ 1,000), which is the number of new links the administrator plans to add to the network one by one.
The i-th line of the following Q lines contains two integer A and B (1 ≤ A ≠ B ≤ N), which is the i-th added new link connecting computer A and B.

The last test case is followed by a line containing two zeros.

Output

For each test case, print a line containing the test case number( beginning with 1) and Q lines, the i-th of which contains a integer indicating the number of bridges in the network after the first i new links are added. Print a blank line after the output for each test case.

Sample Input

复制代码
3 2
1 2
2 3
2
1 2
1 3
4 4
1 2
2 1
2 3
1 4
2
1 2
3 4
0 0
复制代码

Sample Output

复制代码
Case 1:
1
0

Case 2:
2
0

复制代码

先看看别人的思路与题解

题意:

给你N个点M条边的无向图,并且有Q次加边(指定边),问每次加边之后图中的桥的数量

显然,如果加入的边的两个端点在同一个边双内,那么桥的数量不变。所以我们先用Tarjan对原图进行边双连通分量缩点得到一棵树。

接着,对于两个端点不在一个边双的情况,显然桥的数量减少量等于两个端点的树上距离。我们求出树上距离,然后把两端点之间的边标记起来,即边长由原来的1改成0。每次求树上距离时就先一个个地往上爬,顺便还可以标记边。时间复杂度为O(M+QN),可以通过本题,但显然不优。

既然边长变成0了,我们以后都没必要再管这些边了,所以我们可以用缩树的办法,用并查集把两个端点之间的点合并到一个集合中去,然后下次爬到这两个端点处时直接跳到LCA的位置就好了。

题解:

1.利用Tarjan算法,求出每个边双联通分量,并且记录每个点属于哪一个分量。

2.将每一个边双联通分量缩成一个点,最终得到一棵树。而我们想要得到一棵有根树,怎么办?其实在执行Tarjan算法的时候,就已经形成了一个有根树。所以我们只需要在Tarjan算法的基础上,再记录每一个点的父节点以及深度就可以了。

3.每次询问的时候,如果两个点在同一个分量中,那么他们的相连不会减少桥的个数。如果两个点在不同的分量中,那么u->LCA(u,v)和v->LCA(u,v)上路径上的桥,都可以减少,路径上的点都可以缩成一个点,即合并成一个分量。

 

对于缩点的处理:

  方法一:对于一个分量,可以设置一个点为实点,其余的点为虚点。实点即代表着这个分量的所有信息,虚点虽然属于这个分量的点,但是却对他视而不见。我们要做的,就是在这个分量里选择一个点,去代表整个分量。

  方法二:同样地,我们也需要为每一个分量选出一个代表,以表示这个分量。与方法一的“视而不见”不同的是,方法二对每一个点都设置了一个归属集合,即表示这个点属于哪一个集合。由于在处理的过程中,一个集合可能又会被另一个集合所包含,所以我们可以利用并查集的路径压缩,很快地找到一个点的最终所属集合。

方法一:

  1 #include <iostream>
  2 #include <cstdio>
  3 #include <cstring>
  4 #include <cmath>
  5 #include <algorithm>
  6 #include <vector>
  7 #include <queue>
  8 #include <stack>
  9 #include <map>
 10 #include <string>
 11 #include <set>
 12 #define ms(a,b) memset((a),(b),sizeof((a)))
 13 using namespace std;
 14 typedef long long LL;
 15 const double EPS = 1e-8;
 16 const int INF = 2e9;
 17 const LL LNF = 2e18;
 18 const int MAXN = 1e5+10;
 19 
 20 struct Edge
 21 {
 22     int to, next;
 23 }edge[MAXN*8];
 24 int tot, head[MAXN];
 25 
 26 int index, dfn[MAXN], low[MAXN];
 27 int isbridge[MAXN], sum_bridge;
 28 int fa[MAXN], depth[MAXN];
 29 
 30 void addedge(int u, int v)
 31 {
 32     edge[tot].to = v;
 33     edge[tot].next = head[u];
 34     head[u] = tot++;
 35 }
 36 
 37 void Tarjan(int u, int pre)
 38 {
 39     dfn[u] = low[u] = ++index;
 40     depth[u] = depth[pre] + 1;  //记录深度
 41     fa[u] = pre;        //记录父亲结点
 42     for(int i = head[u]; i!=-1; i = edge[i].next)
 43     {
 44         int v = edge[i].to;
 45         if(v==pre) continue;
 46         if(!dfn[v])
 47         {
 48             Tarjan(v, u);
 49             low[u] = min(low[u], low[v]);
 50             if(low[v]>dfn[u])   //isbridge[v]表示在树中,以v为儿子结点的边是否为桥
 51                 isbridge[v] = 1, sum_bridge++;
 52         }
 53         else
 54             low[u] = min(low[u], dfn[v]);
 55     }
 56 }
 57 
 58 void LCA(int u, int v)
 59 {
 60     if(depth[u]<depth[v]) swap(u, v);
 61     while(depth[u]>depth[v])    //深度大的先往上爬。遇到桥,就把它删去。
 62     {
 63         if(isbridge[u]) sum_bridge--, isbridge[u] = 0;
 64         u = fa[u];
 65     }
 66     while(u!=v) //当深度一样时,一起爬。遇到桥,就把它删去。
 67     {
 68         if(isbridge[u]) sum_bridge--, isbridge[u] = 0;
 69         u = fa[u];
 70         if(isbridge[v]) sum_bridge--, isbridge[v] = 0;
 71         v = fa[v];
 72     }
 73 }
 74 
 75 void init()
 76 {
 77     tot = 0;
 78     memset(head, -1, sizeof(head));
 79 
 80     index = 0;
 81     memset(dfn, 0, sizeof(dfn));
 82     memset(low, 0, sizeof(low));
 83     memset(isbridge, 0, sizeof(isbridge));
 84 
 85     sum_bridge = 0;
 86 }
 87 
 88 int main()
 89 {
 90     int n, m, kase = 0;
 91     while(scanf("%d%d", &n, &m) && (n||m) )
 92     {
 93         init();
 94         for(int i = 1; i<=m; i++)
 95         {
 96             int u, v;
 97             scanf("%d%d", &u, &v);
 98             addedge(u, v);
 99             addedge(v, u);
100         }
101 
102         depth[1] = 0;
103         Tarjan(1, 1);
104         int q, a, b;
105         scanf("%d", &q);
106         printf("Case %d:\n", ++kase);
107         while(q--)
108         {
109             scanf("%d%d", &a, &b);
110             LCA(a, b);
111             printf("%d\n", sum_bridge);
112         }
113         printf("\n");
114     }
115 }
View Code

方法二:

  1 #include <iostream>
  2 #include <cstdio>
  3 #include <cstring>
  4 #include <cmath>
  5 #include <algorithm>
  6 #include <vector>
  7 #include <queue>
  8 #include <stack>
  9 #include <map>
 10 #include <string>
 11 #include <set>
 12 #define ms(a,b) memset((a),(b),sizeof((a)))
 13 using namespace std;
 14 typedef long long LL;
 15 const double EPS = 1e-8;
 16 const int INF = 2e9;
 17 const LL LNF = 2e18;
 18 const int MAXN = 1e6+10;
 19 
 20 struct Edge
 21 {
 22     int to, next;
 23 }edge[MAXN], edge0[MAXN];   //edge为初始图, edge0为重建图
 24 int tot, head[MAXN], tot0, head0[MAXN];
 25 
 26 int index, dfn[MAXN], low[MAXN];
 27 int top, Stack[MAXN], instack[MAXN];
 28 int belong[MAXN];
 29 int fa[MAXN], depth[MAXN];  //fa用于重建图时记录当前节点的父亲节点,depth记录当前节点的深度
 30 int sum_bridge;
 31 
 32 //找到x最终所属的结合
 33 int find(int x) { return belong[x]==x?x:belong[x]=find(belong[x]); }
 34 
 35 void addedge(int u, int v, Edge edge[], int head[], int &tot)
 36 {
 37     edge[tot].to = v;
 38     edge[tot].next = head[u];
 39     head[u] = tot++;
 40 }
 41 
 42 void Tarjan(int u, int pre)
 43 {
 44     dfn[u] = low[u] = ++index;
 45     Stack[top++] = u;
 46     instack[u] = true;
 47     for(int i = head[u]; i!=-1; i = edge[i].next)
 48     {
 49         int v = edge[i].to;
 50         if(v==pre) continue;
 51         if(!dfn[v])
 52         {
 53             Tarjan(v, u);
 54             low[u] = min(low[u], low[v]);
 55             if(low[v]>dfn[u]) sum_bridge++;
 56         }
 57         else if(instack[v])
 58             low[u] = min(low[u], dfn[v]);
 59     }
 60 
 61     if(dfn[u]==low[u])
 62     {
 63         int v;
 64         do
 65         {
 66             v = Stack[--top];
 67             instack[v] = false;
 68             belong[v] = u;  //把集合的编号设为联通分量的第一个点
 69         }while(v!=u);
 70     }
 71 }
 72 
 73 void build(int u, int pre)
 74 {
 75     fa[u] = pre;    //记录父亲节点
 76     depth[u] = depth[pre] + 1;  //记录深度
 77     for(int i  = head0[u]; i!=-1; i=edge0[i].next)
 78         if(edge0[i].to!=pre)    //防止往回走
 79             build(edge0[i].to, u);
 80 }
 81 
 82 
 83 int LCA(int u, int v)   //左一步右一步地找LCA
 84 {
 85     if(u==v) return u;  //因为两个结点一定有LCA, 所以一定有u==v的时候
 86 
 87     //可能爬一步就爬了几个深度,因为中间的结点已经往上缩点了
 88     if(depth[u]<depth[v]) swap(u, v);   //深度大的往上爬
 89     sum_bridge--;
 90     int lca = LCA(find(fa[u]), v);
 91     return belong[u] = lca;     //找到了LCA,在沿路返回的时候把当前节点的所属集合置为LCA的所属集合
 92 }
 93 
 94 void init()
 95 {
 96     tot = tot0 = 0;
 97     memset(head, -1, sizeof(head));
 98     memset(head0, -1, sizeof(head0));
 99 
100     index = top = 0;
101     memset(dfn, 0, sizeof(dfn));
102     memset(low, 0, sizeof(low));
103     memset(instack, 0, sizeof(instack));
104 
105     sum_bridge = 0;
106 }
107 
108 int main()
109 {
110     int n, m, kase = 0;
111     while(scanf("%d%d", &n, &m) && (n||m) )
112     {
113         init();
114         for(int i = 1; i<=m; i++)
115         {
116             int u, v;
117             scanf("%d%d", &u, &v);
118             addedge(u, v, edge, head, tot);
119             addedge(v, u, edge, head, tot);
120         }
121 
122         Tarjan(1, 1);
123         for(int u = 1; u<=n; u++)   //重建建图
124         for(int i = head[u]; i!=-1; i = edge[i].next)
125         {
126             int tmpu = find(u);
127             int tmpv = find(edge[i].to);
128             if(tmpu!=tmpv)
129                 addedge(tmpu, tmpv, edge0, head0, tot0);
130         }
131 
132         depth[find(1)] = 0;
133         build(find(1), find(1));    //把无根树转为有根树
134 
135         int q, a, b;
136         scanf("%d", &q);
137         printf("Case %d:\n", ++kase);
138         while(q--)
139         {
140             scanf("%d%d", &a, &b);
141             LCA(find(a), find(b));
142             printf("%d\n", sum_bridge);
143         }
144         printf("\n");
145     }
146 }
View Code

 

 

题目大意:n个点的无向图 初始化有m条边

之后q次操作 每次表示在点a与点b间搭建一条边 输出对于q次操作 每次剩下的桥的条数

 

初始化可以用tarjan算法求出桥 对于不是割边的两个点 就可以算是在一个集合中 这样用并查集就可以进行缩点

最后生成的就是一棵树 树边就是图中的所有桥 q次询问中 每次加边<u,v> 如果u和v在一个集合中 说明新的边不会造成影响

如果u和v在两个集合中 两个集合间的边在添加<u,v>后就会失去桥的性质 这样通过LCA就可以遍历所有两个集合间的集合 在加上<u,v>这条边后 这两个集合间的集合其实就变成了一个环 也就是可以缩成一个点 在合并集合的过程中 就可以把消失的桥从总和中减去了


之前一直在想为什么要用LCA来做这道题,原来他们缩点之后会形成一棵树,然后因为已经经过缩点了,所以这些树上的边都是桥(终于理解为什么他们说缩点之后的树边为桥了),那么如果加入的这条边是属于一个缩点的话(缩点里面的点算是一个集合)那么就对原图中的桥没有任何影响,但是如果加入的边是属于两个缩点的话,那么就会形成一个环,那么任意删除这个环里面的一条边,这棵树还是互通的,那么就可以利用LCA的特性去算出到底减少了多少条桥了,因为是最近公共祖先,那么新加入的这条边的两个点通过LCA找到对方肯定是走最短的路径(在树上走最小的边)那么就可以得到结果了,总桥数减去走LCA上的边就是题目要的答案了!!!!

  1 #include <stdio.h>
  2 #include <string.h>
  3 #include <algorithm>
  4 #include <stack>
  5 using namespace std;
  6 #define N 100010
  7 #define M 400010
  8 
  9 struct edge{
 10     int v;
 11     int next;
 12 }Edge[M];//边的集合
 13 
 14 int node[N];//顶点集合
 15 int DFN[N];//节点u搜索的序号(时间戳)
 16 int LOW[N];//u或u的子树能够追溯到的最早的栈中节点的序号(时间戳)
 17 int fa[N];//上一个节点 
 18 int pre[N];//并查集父亲节点 
 19 int n,m;//n:点的个数;m:边的条数
 20 int cnt_edge;//边的计数器
 21 int Index;//序号(时间戳)
 22 int ans;//桥的个数 
 23 
 24 
 25 void init()//初始化,注意不要把n初始为0 
 26 {
 27     cnt_edge=0;
 28     Index=0;
 29     ans=0;
 30     memset(Edge,0,sizeof(Edge));
 31     memset(node,-1,sizeof(node));
 32     memset(DFN,0,sizeof(DFN));
 33     memset(LOW,0,sizeof(LOW));
 34     memset(fa,0,sizeof(fa));
 35     memset(pre,0,sizeof(pre));
 36     for(int i=1;i<=n;i++)
 37     {
 38         pre[i]=i;
 39     }
 40 }
 41 
 42 int Find(int x)
 43 {
 44 //    while(n!=pre[n])//写成这样也行,只不过没有缩短路径 45 //    {
 46 //        n=pre[n];
 47 //    }
 48 //    return n;
 49     return pre[x] == x? pre[x]: (pre[x] = Find(pre[x])); 
 50 }
 51 
 52 int Union(int u,int v)
 53 {
 54     int uu,vv;
 55     uu=Find(u);
 56     vv=Find(v);
 57     if(vv==uu)
 58         return 0;
 59     pre[uu]=vv;
 60     return 1;
 61 }
 62 
 63 void add_edge(int u,int v)//邻接表存储
 64 {
 65     Edge[cnt_edge].next=node[u];
 66     Edge[cnt_edge].v=v;
 67     node[u]=cnt_edge++;
 68 }
 69 
 70 void tarjan(int u)
 71 {
 72     DFN[u]=LOW[u]=Index++;
 73     for(int i=node[u];i!=-1;i=Edge[i].next)
 74     {
 75         int v=Edge[i].v;
 76         if(v==fa[u]) //这个要写前面,否则会出错
 77             continue;
 78         if(!DFN[v])//如果点v没被访问
 79         {
 80             fa[v]=u;
 81             tarjan(v);
 82             LOW[u]=min(LOW[u],LOW[v]);
 83             if(LOW[v]>DFN[u])
 84             {
 85                 ans++;
 86             }
 87             else Union(v,u);
 88         }
 89         else //if(v!=fa[u]) //如果点v已经被访问过
 90             LOW[u]=min(LOW[u],DFN[v]);  
 91     }
 92 }
 93 
 94 void LCA(int u,int v)
 95 {
 96     if(DFN[v]<DFN[u])
 97         swap(u,v);
 98     while(DFN[v]>DFN[u])
 99     {
100         if(Union(v,fa[v]))
101             ans--;
102         v=fa[v];
103     }
104     while(v!=u)
105     {
106         if(Union(u,fa[u]))
107             ans--;
108         u=fa[u];
109     }
110 }
111 
112 int main()
113 {
114     //freopen("sample.txt","r",stdin);
115     int tot=0;
116     while(~scanf("%d %d",&n,&m)&&(m+n))
117     {
118         init();
119         while(m--)
120         {
121             int u,v;
122             scanf("%d %d",&u,&v);
123             add_edge(u,v);
124             add_edge(v,u);
125         }
126         fa[1]=1;
127         for(int i=1;i<=n;i++)
128         {
129             if(!DFN[i])
130             {
131                 tarjan(i);
132             }
133         }
134         int q;
135         scanf("%d",&q);
136         printf("Case %d:\n",++tot);
137         while(q--)
138         {
139             int u,v;
140             scanf("%d %d",&u,&v);
141             LCA(u,v);
142             printf("%d\n",ans);
143             
144         }
145         printf("\n");
146     }
147     return  0;
148 }

 


POJ-3177 Redundant Paths(Tarjan求桥、边双连通分量)

Description

In order to get from one of the F (1 <= F <= 5,000) grazing fields (which are numbered 1..F) to another field, Bessie and the rest of the herd are forced to cross near the Tree of Rotten Apples. The cows are now tired of often being forced to take a particular path and want to build some new paths so that they will always have a choice of at least two separate routes between any pair of fields. They currently have at least one route between each pair of fields and want to have at least two. Of course, they can only travel on Official Paths when they move from one field to another. 

Given a description of the current set of R (F-1 <= R <= 10,000) paths that each connect exactly two different fields, determine the minimum number of new paths (each of which connects exactly two fields) that must be built so that there are at least two separate routes between any pair of fields. Routes are considered separate if they use none of the same paths, even if they visit the same intermediate field along the way. 

There might already be more than one paths between the same pair of fields, and you may also build a new path that connects the same fields as some other path.

Input

Line 1: Two space-separated integers: F and R 

Lines 2..R+1: Each line contains two space-separated integers which are the fields at the endpoints of some path.

Output

Line 1: A single integer that is the number of new paths that must be built.

Sample Input

复制代码
7 7
1 2
2 3
3 4
2 5
4 5
5 6
5 7
复制代码

Sample Output

 2

Hint

Explanation of the sample: 

Check some of the routes: 
1 – 2: 1 –> 2 and 1 –> 6 –> 5 –> 2 
1 – 4: 1 –> 2 –> 3 –> 4 and 1 –> 6 –> 5 –> 4 
3 – 7: 3 –> 4 –> 7 and 3 –> 2 –> 5 –> 7 
Every pair of fields is, in fact, connected by two routes. 

It's possible that adding some other path will also solve the problem (like one from 6 to 7). Adding two paths, however, is the minimum.

题意:

求一个无向图中还需加入多少条边能构成一个边双连通分量
题解:

看起来很厉害的样子,其实还是先用Tarjan缩点,然后,枚举每一条边,看左右两个端点缩点后是否在同一个点中,如果不在连边,其实只要更新每点的出度即可,最后统计出度为1的点的个数ans,由求“加入多少条边能构成一个边双连通分量”的方法可知,答案为(ans+1)/2。

 

  1 #include <stdio.h>
  2 #include <iostream>
  3 #include <string.h>
  4 #include <algorithm>
  5 #include <stack>
  6 #include <string>
  7 #include <sstream>
  8 using namespace std;
  9 #define N 5005 
 10 #define M 10010
 11 
 12 struct Edge{
 13     int v;
 14     int next;
 15     int cut;
 16 }Edge[M*4];//边的集合
 17 
 18 int node[N];//顶点集合
 19 int instack[N];//标记是否在stack中
 20 int Belong[N];//各顶点属于哪个强连通分量
 21 int DFN[N];//节点u搜索的序号(时间戳)
 22 int LOW[N];//u或u的子树能够追溯到的最早的栈中节点的序号(时间戳)
 23 int n,m;//n:点的个数
 24 int cnt_edge;//边的计数器
 25 int Index;//序号(时间戳)
 26 int ans;
 27 int Bcnt; //有多少个强连通分量
 28 int out[N];//存储出度 
 29 int in[N];//存储入度 
 30 int fa[N];
 31 stack<int> sk;
 32 
 33 void init()
 34 {
 35     cnt_edge=0;
 36     Index=0;
 37     ans=0;
 38     Bcnt=0;
 39     memset(Edge,0,sizeof(Edge));
 40     memset(node,-1,sizeof(node));
 41     memset(DFN,0,sizeof(DFN));
 42     memset(LOW,0,sizeof(LOW));
 43     memset(fa,0,sizeof(fa));
 44     memset(out,0,sizeof(out));
 45     while(!sk.empty())//清空栈 
 46     {    
 47         sk.pop();
 48     }
 49 }
 50 
 51 void add_edge(int u,int v)//邻接表存储
 52 {
 53     Edge[cnt_edge].next=node[u];
 54     Edge[cnt_edge].v=v;
 55     Edge[cnt_edge].cut=0;
 56     node[u]=cnt_edge++;
 57 }
 58 
 59 void tarjan(int u)
 60 {
 61     DFN[u]=LOW[u]=++Index;
 62     instack[u]=1;
 63     sk.push(u);
 64     int flag=0;
 65     for(int i=node[u];i!=-1;i=Edge[i].next)
 66     {
 67         int v=Edge[i].v;
 68         if(!flag&&v==fa[u])
 69         {
 70             flag=1;
 71             continue;
 72         }
 73             
 74         if(!DFN[v])//如果点v没被访问
 75         {
 76             fa[v]=u;
 77             tarjan(v);
 78             LOW[u]=min(LOW[u],LOW[v]);
 79             if(LOW[v]>DFN[u])
 80             {
 81                 Edge[i].cut=1;
 82                 Edge[i^1].cut=1;//让存储边的Edge后一位的cut也为1 
 83             }
 84         }
 85         else //如果点v已经被访问过
 86         {
 87             if(instack[v])
 88                 LOW[u]=min(DFN[v],LOW[u]);
 89         }    
 90     }
 91     if(DFN[u]==LOW[u])
 92     {
 93         Bcnt++;
 94         int t;
 95         do{
 96             t=sk.top();
 97             sk.pop();
 98             instack[t]=0;
 99             Belong[t]=Bcnt;
100         }while(t!=u);
101     }
102 
103 }
104 
105 void solve()
106 {
107     for(int i=1;i<=n;i++)
108     {
109         for(int j=node[i];j!=-1;j=Edge[j].next)
110         {
111 //中间和下面注释中任选一种方法 
112             if(Edge[j].cut==1)
113                 out[Belong[i]]++;
114 //            int v=Edge[j].v;
115 //            if(Belong[i]!=Belong[v])
116 //            {
117 //                out[Belong[i]]++;
118 //            }
119         }
120     }
121     int ans=0;
122     for(int i=1;i<=Bcnt;i++)
123     {
124         if(out[i]==1) 
125             ans++;
126     }
127     printf("%d\n",(ans+1)/2);
128 }
129 
130 int main()
131 {
132     //freopen("sample.txt","r",stdin);
133     while(~scanf("%d %d",&n,&m))
134     {
135         init();
136         while(m--)
137         {
138             int u,v;
139             scanf("%d %d",&u,&v);
140             add_edge(u,v);
141             add_edge(v,u);
142         }
143         for(int i=1;i<=n;i++)
144             {
145             if(!DFN[i])
146             {
147                 tarjan(i);
148             }
149          }
150          solve();
151     } 
152     return  0;
153 }

 


 

HDU-4612 Warm up(边双连通分量+树的直径)

http://acm.hdu.edu.cn/showproblem.php?pid=4612

Problem Description

  N planets are connected by M bidirectional channels that allow instant transportation. It's always possible to travel between any two planets through these channels.
  If we can isolate some planets from others by breaking only one channel , the channel is called a bridge of the transportation system.
People don't like to be isolated. So they ask what's the minimal number of bridges they can have if they decide to build a new channel.
  Note that there could be more than one channel between two planets.

Input

  The input contains multiple cases.
  Each case starts with two positive integers N and M , indicating the number of planets and the number of channels.
  (2<=N<=200000, 1<=M<=1000000)
  Next M lines each contains two positive integers A and B, indicating a channel between planet A and B in the system. Planets are numbered by 1..N.
  A line with two integers '0' terminates the input.

Output

  For each case, output the minimal number of bridges after building a new channel in a line.

Sample Input

4 4
1 2
1 3
1 4
2 3
0 0

Sample Output

 0

 

 题意:给出一个无向图,可能包含重边和自环,现在可以在这个图上加上一条边,问加完这条边后图上最少有多少桥边

思路:首先求出所有边双连通分量,然后缩点,缩点以后形成了一棵树,那么只要我们将树上最长的一条链变成一个环,那么肯定可以减少最多的桥边(在树的直径两边加边是优的),问题就变成了求出边双连通分量,注意要考虑清楚重边的影响。

缩点后 两遍DFS搜直径就好,第一遍DFS求出的点p为最深点或次深点,从p开始第二次DFS求出的点q为次深点或最深点,p到q长度就是直径。求出桥的数量,再减去缩点后形成的树的直径。

答案为:缩点后的边数(桥的数量)-直径边数(最大深度)。

 

  1 #include <stdio.h>
  2 #include <iostream>
  3 #include <string.h>
  4 #include <algorithm>
  5 #include <stack>
  6 #include <string>
  7 #include <sstream>
  8 using namespace std;
  9 const int INF=0x3f3f3f3f;
 10 #define N 200005 
 11 #define M 2000100
 12 //const int maxn=1e6+5; 
 13 
 14 struct edge{
 15     int v;
 16     int next;
 17 }Edge[M];//边的集合
 18 
 19 int node[N];//顶点集合
 20 int instack[N];//标记是否在stack中
 21 int Belong[N];//各顶点属于哪个强连通分量
 22 int DFN[N];//节点u搜索的序号(时间戳)
 23 int LOW[N];//u或u的子树能够追溯到的最早的栈中节点的序号(时间戳)
 24 int n,m;//n:点的个数
 25 int cnt_edge;//边的计数器
 26 int Index;//序号(时间戳)
 27 int ans,root;
 28 int Bcnt; //有多少个强连通分量
 29 int fa[N];
 30 int vis[N];//重建树后各点的深度 
 31 int isbridge[N];//是不是桥 
 32 stack<int> sk;
 33 
 34 void init()
 35 {
 36     cnt_edge=0;
 37     Index=0;
 38     Bcnt=0;
 39     memset(Edge,0,sizeof(Edge));
 40     memset(node,-1,sizeof(node));
 41     memset(DFN,0,sizeof(DFN));
 42     memset(LOW,0,sizeof(LOW));
 43     memset(fa,0,sizeof(fa));
 44     memset(instack,0,sizeof(instack));
 45     memset(Belong,0,sizeof(Belong));
 46     memset(isbridge,-1,sizeof(isbridge));
 47     while(!sk.empty())
 48     {    
 49         sk.pop();
 50     }
 51 }
 52 
 53 void add_edge(int u,int v)//邻接表存储
 54 {
 55     Edge[cnt_edge].next=node[u];
 56     Edge[cnt_edge].v=v;
 57     node[u]=cnt_edge++;
 58 }
 59 
 60 void tarjan(int u)
 61 {
 62     DFN[u]=LOW[u]=++Index;
 63     instack[u]=1;
 64     sk.push(u);
 65     int flag=0;
 66     for(int i=node[u];i!=-1;i=Edge[i].next)
 67     {
 68         int v=Edge[i].v;
 69         if(!flag&&v==fa[u])
 70         {
 71             flag=1;
 72             continue;
 73         }    
 74         if(!DFN[v])//如果点v没被访问
 75         {
 76             fa[v]=u;
 77             tarjan(v);
 78             LOW[u]=min(LOW[u],LOW[v]);
 79             if(LOW[v]>DFN[u])
 80             {
 81                 isbridge[v]=1;
 82             }
 83         }
 84         else if(instack[v]) //如果点v已经被访问过
 85         {
 86             LOW[u]=min(DFN[v],LOW[u]);
 87         }    
 88     }
 89     if(DFN[u]==LOW[u])
 90     {
 91         Bcnt++;
 92         int t;
 93         do{
 94             t=sk.top();
 95             sk.pop();
 96             Belong[t]=Bcnt;
 97             instack[t]=0;
 98         }while(t!=u);
 99     }
100 }
101 
102 void reinit()//重建树前初始化 
103 {
104     memset(node,-1,sizeof(node));
105     memset(vis,-1,sizeof(vis));
106     cnt_edge=0;
107     Index=0;
108 }
109 
110 void DFS(int u,int dist)//对重建的树的深搜 
111 {
112     vis[u]=dist;
113     if(vis[u]>ans)
114     {
115         root=u;
116         ans=vis[u];
117     }
118     for(int j=node[u];j!=-1;j=Edge[j].next)
119     {
120         int v=Edge[j].v;
121         if(vis[v]==-1)
122         {
123             DFS(v,dist+1);
124         }
125     }
126 }
127 
128 void solve()
129 {
130     int tot=0;
131     reinit();
132     for(int i=1;i<=n;i++)
133     {
134         if(isbridge[i]==1)
135         {
136             tot++;
137             add_edge(Belong[fa[i]],Belong[i]);
138             add_edge(Belong[i],Belong[fa[i]]);
139         }
140     }
141     root=1;//从编号为1开始最远的点 
142     ans=0;
143     DFS(1,0);
144     ans=0;//最大深度 
145     memset(vis,-1,sizeof(vis));
146     DFS(root,0);
147     printf("%d\n",tot-ans);
148 }
149 
150 int main()
151 {
152     //freopen("sample.txt","r",stdin);
153     while(~scanf("%d %d",&n,&m)&&(m+n))
154     {
155         init();
156         while(m--)
157         {
158             int u,v;
159             scanf("%d %d",&u,&v);
160             add_edge(u,v);
161             add_edge(v,u);
162         }
163         for(int i=1;i<=n;i++)
164             {
165             if(!DFN[i])
166             {
167                  tarjan(i);
168             }
169          }
170          solve();
171     } 
172     return  0;
173 }

 


 

HDU-4635 Strongly connected ( 强连通分量 )

http://acm.hdu.edu.cn/showproblem.php?pid=4635

Problem Description

Give a simple directed graph with N nodes and M edges. Please tell me the maximum number of the edges you can add that the graph is still a simple directed graph. Also, after you add these edges, this graph must NOT be strongly connected.
A simple directed graph is a directed graph having no multiple edges or graph loops.
A strongly connected digraph is a directed graph in which it is possible to reach any node starting from any other node by traversing edges in the direction(s) in which they point. 

Input

The first line of date is an integer T, which is the number of the text cases.
Then T cases follow, each case starts of two numbers N and M, 1<=N<=100000, 1<=M<=100000, representing the number of nodes and the number of edges, then M lines follow. Each line contains two integers x and y, means that there is a edge from x to y.

Output

For each case, you should output the maximum number of the edges you can add.
If the original graph is strongly connected, just output -1.

Sample Input

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

Sample Output

Case 1: -1
Case 2: 1
Case 3: 15

 

题目大意:

给你N个顶点,M条边的有向图,问最多加入多少条边之后,这个图仍旧是一个简单图(简单图:无重边,无自环),
并且不是强联通的。如果原始的图就是强联通的话就输出 -1.

 

最终的贪心思路就是这样的:

①找一个出度为0,或者是入度为0的节点,并且当前点在原图中的点个数最少。

②将其他各个点构成完全子图(一共需要边:n*n-1【这里n表示其他各点个数和】)

③将这个点内部也构成完全子图

③将这个节点中各点和其他各点相连。(仅有一个方向)

 

可假设最后所求的图由两个强连通图(图U和图V)组成,且图U和图V之前只有单向边,此时既满足整个图不强连通,又能保证有最多的边。

设图U的顶点数为x,图V的顶点数为y  ( x+y=n )

图U强连通,边数最多为 x*(x-1)

图V强连通,边数最多为 y*(y-1)

图U和图V之间只有单向边,则边数最多为 x*y

图的总边数 sum = x*y+x*(x-1)+y*(y-1) = n*n-n-x*y

即可转化为求 x*y 的最小值,即求顶点数最小的强连通分量

具体:

找出强联通块,计算每个连通块内的点数。将点数最少的那个连通块单独拿出来,其余的连通块合并成一个连通分量。

那么假设第一个连通块的点数是 x  第二个连通块的点数是 y, 已经有的边数是 m 则

ans =  x*(x-1) + y*(y-1) + x*y - m;(强联通块内边数最多是 n*(n-1) ),  
 
这里最少点数的强联通分量要满足一个条件,就是出度或者入度为 0才行,不然是不满足的。
  1 #include <stdio.h>
  2 #include <iostream>
  3 #include <string.h>
  4 #include <algorithm>
  5 #include <stack>
  6 #include <string>
  7 #include <sstream>
  8 using namespace std;
  9 const int INF=0x3f3f3f3f;
 10 #define N 100005 
 11 #define M 2000100
 12 //const int maxn=1e6+5; 
 13 
 14 struct edge{
 15     int v;
 16     int next;
 17 }Edge[M];//边的集合
 18 
 19 int node[N];//顶点集合
 20 int instack[N];//标记是否在stack中
 21 int Belong[N];//各顶点属于哪个强连通分量
 22 int DFN[N];//节点u搜索的序号(时间戳)
 23 int LOW[N];//u或u的子树能够追溯到的最早的栈中节点的序号(时间戳)
 24 int n,m;//n:点的个数 m:边的个数 
 25 int cnt_edge;//边的计数器
 26 int Index;//序号(时间戳)
 27 int Bcnt; //有多少个强连通分量
 28 int out[N];
 29 int in[N];
 30 int sum[N];
 31 stack<int> sk;
 32 
 33 void init()
 34 {
 35     cnt_edge=0;
 36     Index=0;
 37     Bcnt=0;
 38     memset(Edge,0,sizeof(Edge));
 39     memset(node,-1,sizeof(node));
 40     memset(DFN,0,sizeof(DFN));
 41     memset(LOW,0,sizeof(LOW));
 42     memset(sum,0,sizeof(sum));
 43     memset(instack,0,sizeof(instack));
 44     memset(Belong,0,sizeof(Belong));
 45     memset(out,0,sizeof(out));
 46     memset(in,0,sizeof(in));
 47     while(!sk.empty())
 48     {    
 49         sk.pop();
 50     }
 51 }
 52 
 53 void add_edge(int u,int v)//邻接表存储
 54 {
 55     Edge[cnt_edge].next=node[u];
 56     Edge[cnt_edge].v=v;
 57     node[u]=cnt_edge++;
 58 }
 59 
 60 void tarjan(int u)
 61 {
 62     DFN[u]=LOW[u]=++Index;
 63     instack[u]=1;
 64     sk.push(u);
 65     for(int i=node[u];i!=-1;i=Edge[i].next)
 66     {
 67         int v=Edge[i].v;
 68         if(!DFN[v])//如果点v没被访问
 69         {
 70             tarjan(v);
 71             LOW[u]=min(LOW[u],LOW[v]);
 72         }
 73         else if(instack[v]) //如果点v已经被访问过
 74         {
 75             LOW[u]=min(DFN[v],LOW[u]);
 76         }    
 77     }
 78     if(DFN[u]==LOW[u])
 79     {
 80         Bcnt++;
 81         int t;
 82         do{
 83             t=sk.top();
 84             sk.pop();
 85             Belong[t]=Bcnt;
 86             instack[t]=0;
 87             sum[Bcnt]++;
 88         }while(t!=u);
 89     }
 90 }
 91 
 92 void solve()
 93 {
 94     for(int i=1;i<=n;i++)
 95     {
 96         for(int j=node[i];j!=-1;j=Edge[j].next)
 97         {
 98             int v=Edge[j].v;
 99             if(Belong[i]!=Belong[v])
100             {
101                 out[Belong[i]]++;
102                 in[Belong[v]]++;
103             }
104         }
105     }
106     int x=INF;
107     for(int i=1;i<=Bcnt;i++)
108     {
109         if(!out[i]||!in[i])
110             x=min(x,sum[i]);    
111     }
112     int y=n-x;
113     int ans=x*(x-1)+y*(y-1)+x*y-m;
114     if(Bcnt==1)
115         printf("-1\n");
116     else
117         printf("%d\n",ans);
118 }
119 
120 int main()
121 {
122     //freopen("sample.txt","r",stdin);
123     int t;
124     scanf("%d",&t);
125     for(int g=1;g<=t;g++)
126     {
127         scanf("%d %d",&n,&m);
128         init();
129         for(int i=1;i<=m;i++)
130         {
131             int u,v;
132             scanf("%d %d",&u,&v);
133             add_edge(u,v);
134         }
135         for(int i=1;i<=n;i++)
136             {
137             if(!DFN[i])
138             {
139                  tarjan(i);
140             }
141          }
142          printf("Case %d: ",g);
143          solve();
144     } 
145     return  0;
146 }

 


 

 

 

 

 


 

HDU-4738 Caocao's Bridges(Tarjan求桥+重边判断)

http://acm.hdu.edu.cn/showproblem.php?pid=4738

Problem Description

Caocao was defeated by Zhuge Liang and Zhou Yu in the battle of Chibi. But he wouldn't give up. Caocao's army still was not good at water battles, so he came up with another idea. He built many islands in the Changjiang river, and based on those islands, Caocao's army could easily attack Zhou Yu's troop. Caocao also built bridges connecting islands. If all islands were connected by bridges, Caocao's army could be deployed very conveniently among those islands. Zhou Yu couldn't stand with that, so he wanted to destroy some Caocao's bridges so one or more islands would be seperated from other islands. But Zhou Yu had only one bomb which was left by Zhuge Liang, so he could only destroy one bridge. Zhou Yu must send someone carrying the bomb to destroy the bridge. There might be guards on bridges. The soldier number of the bombing team couldn't be less than the guard number of a bridge, or the mission would fail. Please figure out as least how many soldiers Zhou Yu have to sent to complete the island seperating mission.

Input

There are no more than 12 test cases.
In each test case:
The first line contains two integers, N and M, meaning that there are N islands and M bridges. All the islands are numbered from 1 to N. ( 2 <= N <= 1000, 0 < M <= N 2 )
Next M lines describes M bridges. Each line contains three integers U,V and W, meaning that there is a bridge connecting island U and island V, and there are W guards on that bridge. ( U ≠ V and 0 <= W <= 10,000 )
The input ends with N = 0 and M = 0.

Output

For each test case, print the minimum soldier number Zhou Yu had to send to complete the mission. If Zhou Yu couldn't succeed any way, print -1 instead.

Sample Input

3 3
1 2 7
2 3 4
3 1 4
3 2
1 2 7
2 3 4
0 0

Sample Output

-1
4

题意:

求出权值最小的桥,可重边

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

分析:

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

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

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

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

  1 #include <stdio.h>
  2 #include <iostream>
  3 #include <string.h>
  4 #include <algorithm>
  5 #include <stack>
  6 #include <string>
  7 #include <sstream>
  8 using namespace std;
  9 const int INF=0x3f3f3f3f;
 10 #define N 1005 
 11 #define M 100010
 12 
 13 
 14 struct edge{
 15     int v;
 16     int next;
 17     int person;
 18     int cut;
 19 }Edge[N*N];//边的集合
 20 
 21 int node[N];//顶点集合
 22 int instack[N];//标记是否在stack中
 23 int Belong[N];//各顶点属于哪个强连通分量
 24 int DFN[N];//节点u搜索的序号(时间戳)
 25 int LOW[N];//u或u的子树能够追溯到的最早的栈中节点的序号(时间戳)
 26 int n,m;//n:点的个数
 27 int cnt_edge;//边的计数器
 28 int Index;//序号(时间戳)
 29 int ans;
 30 int Bcnt; //有多少个强连通分量
 31 int fa[N];
 32 int cs;
 33 int bridge[N];
 34 stack<int> sk;
 35 
 36 void init()
 37 {
 38     cnt_edge=0;
 39     Index=0;
 40     ans=0;
 41     Bcnt=0;
 42     cs=0;
 43     memset(Edge,0,sizeof(Edge));
 44     memset(node,-1,sizeof(node));
 45     memset(DFN,0,sizeof(DFN));
 46     memset(LOW,0,sizeof(LOW));
 47     memset(fa,0,sizeof(fa));
 48     memset(instack,0,sizeof(instack));
 49     memset(Belong,0,sizeof(Belong));
 50     while(!sk.empty())
 51     {    
 52         sk.pop();
 53     }
 54 }
 55 
 56 void add_edge(int u,int v,int q)//邻接表存储
 57 {
 58     Edge[cnt_edge].next=node[u];
 59     Edge[cnt_edge].v=v;
 60     Edge[cnt_edge].person=q;
 61     Edge[cnt_edge].cut=0;
 62     node[u]=cnt_edge++;
 63 }
 64 
 65 void tarjan(int u)
 66 {
 67     DFN[u]=LOW[u]=++Index;
 68     int flag=0;
 69     for(int i=node[u];i!=-1;i=Edge[i].next)
 70     {
 71         int v=Edge[i].v;
 72         if(!flag&&v==fa[u])
 73         {
 74             flag=1;
 75             continue;
 76         }
 77             
 78         if(!DFN[v])//如果点v没被访问
 79         {
 80             fa[v]=u;
 81             tarjan(v);
 82             LOW[u]=min(LOW[u],LOW[v]);
 83             if(LOW[v]>DFN[u])
 84             {
 85                 Edge[i].cut=1;
 86                 Edge[i^1].cut=1;
 87             }
 88         }
 89         else //如果点v已经被访问过
 90         {
 91             if(DFN[v])
 92                 LOW[u]=min(DFN[v],LOW[u]);
 93         }    
 94     }
 95 }
 96 
 97 void solve()
 98 {
 99     if(cs>=2)
100         printf("0\n");
101     else
102     {
103         int flag=0;
104         int Min=INF;
105         for(int i=1;i<=n;i++)
106         {
107             for(int j=node[i];j!=-1;j=Edge[j].next)
108             {
109                 if(Edge[j].cut)
110                 {
111                     flag=1;
112                     Min=min(Min,Edge[j].person);
113                 }
114             }        
115         }
116         if(!flag)
117             printf("-1\n");
118         else
119         {
120             if(Min==0)
121                 printf("1\n");
122             else 
123                 printf("%d\n",Min);
124         }
125         
126     }
127 }
128 
129 int main()
130 {
131    // freopen("sample.txt","r",stdin);
132     while(~scanf("%d %d",&n,&m)&&(m+n))
133     {
134         init();
135         while(m--)
136         {
137             int u,v,q;
138             scanf("%d %d %d",&u,&v,&q);
139             add_edge(u,v,q);
140             add_edge(v,u,q);
141         }
142         for(int i=1;i<=n;i++)
143             {
144             if(!DFN[i])
145             {
146                     cs++;
147                  tarjan(i);
148             }
149          }
150          solve();
151     } 
152     return  0;
153 }

 


 

转载于:https://www.cnblogs.com/jiamian/p/11211913.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值