tarjan算法入门整理专题(判断是否是一个强连通、通过缩点求至少加几条边让整个图变成强连通和传递的最小费用)

99 篇文章 0 订阅
63 篇文章 0 订阅

先给出模板:(注:模板参考自九野的博客)

时间复杂度为O(n+m)

黑匣子:

先最初调用

1、init()

2、把图用add 存下来,注意图点标为1-n,若是[0,n-1]则给所有点++;

3、调用tarjan_init(n); 再调用suodian();

4、新图就是vector<int>G[];  新图点标从1-tar ;

5、对于原图中的每个点u,都属于新图中的一个新点Belong[u];

新图一定是森林。

6、新图中的点u 所表示的环对应原图中的vector<int> bcc[u];

7、旧图中u在新图中所属的点是Belong[u];

#define N 30100
//N为最大点数
#define M 150100
//M为最大边数
int n, m;//n m 为点数和边数

struct Edge{
	int from, to, nex;
	bool sign;//是否为桥
}edge[M<<1];
int head[N], edgenum;
void add(int u, int v){//边的起点和终点
	Edge E={u, v, head[u], false};
	edge[edgenum] = E;
	head[u] = edgenum++;
}

int DFN[N], Low[N], Stack[N], top, Time; //Low[u]是点集{u点及以u点为根的子树} 中(所有反向弧)能指向的(离根最近的祖先v) 的DFN[v]值(即v点时间戳)
int taj;//连通分支标号,从1开始
int Belong[N];//Belong[i] 表示i点属于的连通分支
bool Instack[N];
vector<int> bcc[N]; //标号从1开始

void tarjan(int u ,int fa){  
	DFN[u] = Low[u] = ++ Time ;  
	Stack[top ++ ] = u ;  
	Instack[u] = 1 ;  

	for (int i = head[u] ; ~i ; i = edge[i].nex ){  
		int v = edge[i].to ;  
		if(DFN[v] == -1)
		{  
			tarjan(v , u) ;  
			Low[u] = min(Low[u] ,Low[v]) ;
			if(DFN[u] < Low[v])
			{
				edge[i].sign = 1;//为割桥
			}
		}  
		else if(Instack[v]) Low[u] = min(Low[u] ,DFN[v]) ; 		
	}  
	if(Low[u] == DFN[u]){  
		int now;
		taj ++ ; bcc[taj].clear();
		do{
			now = Stack[-- top] ;  
			Instack[now] = 0 ; 
			Belong [now] = taj ;
			bcc[taj].push_back(now);
		}while(now != u) ;
	}
}

void tarjan_init(int all){
	memset(DFN, -1, sizeof(DFN));
	memset(Instack, 0, sizeof(Instack));
	top = Time = taj = 0;
	for(int i=1;i<=all;i++)if(DFN[i]==-1 )tarjan(i, i); //注意开始点标!!!
}
vector<int>G[N];
int du[N];
void suodian(){
	memset(du, 0, sizeof(du));
	for(int i = 1; i <= taj; i++)G[i].clear();
	for(int i = 0; i < edgenum; i++){
		int u = Belong[edge[i].from], v = Belong[edge[i].to];
		if(u!=v)G[u].push_back(v), du[v]++;
	}
}
void init(){memset(head, -1, sizeof(head)); edgenum=0;}


下面为例题:


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


迷宫城堡

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)
Total Submission(s): 10277    Accepted Submission(s): 4621


Problem Description
为了训练小希的方向感,Gardon建立了一座大城堡,里面有N个房间(N<=10000)和M条通道(M<=100000),每个通道都是单向的,就是说若称某通道连通了A房间和B房间,只说明可以通过这个通道由A房间到达B房间,但并不说明通过它可以由B房间到达A房间。Gardon需要请你写个程序确认一下是否任意两个房间都是相互连通的,即:对于任意的i和j,至少存在一条路径可以从房间i到房间j,也存在一条路径可以从房间j到房间i。
 

Input
输入包含多组数据,输入的第一行有两个数:N和M,接下来的M行每行有两个数a和b,表示了一条通道可以从A房间来到B房间。文件最后以两个0结束。
 

Output
对于输入的每组数据,如果任意两个房间都是相互连接的,输出"Yes",否则输出"No"。
 

Sample Input
  
  
3 3 1 2 2 3 3 1 3 3 1 2 2 3 3 2 0 0
 

Sample Output
  
  
Yes No
 

Author
Gardon
 

Source
 

编程思想:  判断是否是一个强连通。

AC code:

#include <iostream>
#include <cmath>
#include<stdlib.h> 
#include<vector>
#include<cstring>
using namespace std;
#define N 30100
//N为最大点数
#define M 150100
//M为最大边数
int n, m;//n m 为点数和边数

struct Edge{
	int from, to, nex;
	bool sign;//是否为桥
}edge[M<<1];
int head[N], edgenum;
void add(int u, int v){//边的起点和终点
	Edge E={u, v, head[u], false};
	edge[edgenum] = E;
	head[u] = edgenum++;
}

int DFN[N], Low[N], Stack[N], top, Time; //Low[u]是点集{u点及以u点为根的子树} 中(所有反向弧)能指向的(离根最近的祖先v) 的DFN[v]值(即v点时间戳)
int taj;//连通分支标号,从1开始
int Belong[N];//Belong[i] 表示i点属于的连通分支
bool Instack[N];
vector<int> bcc[N]; //标号从1开始

void tarjan(int u ,int fa){  
	DFN[u] = Low[u] = ++ Time ;  
	Stack[top ++ ] = u ;  
	Instack[u] = 1 ;  

	for (int i = head[u] ; ~i ; i = edge[i].nex ){  
		int v = edge[i].to ;  
		if(DFN[v] == -1)
		{  
			tarjan(v , u) ;  
			Low[u] = min(Low[u] ,Low[v]) ;
			if(DFN[u] < Low[v])
			{
				edge[i].sign = 1;//为割桥
			}
		}  
		else if(Instack[v]) Low[u] = min(Low[u] ,DFN[v]) ; 		
	}  
	if(Low[u] == DFN[u]){  
		int now;
		taj ++ ; bcc[taj].clear();
		do{
			now = Stack[-- top] ;  
			Instack[now] = 0 ; 
			Belong [now] = taj ;
			bcc[taj].push_back(now);
		}while(now != u) ;
	}
}

void tarjan_init(int all){
	memset(DFN, -1, sizeof(DFN));
	memset(Instack, 0, sizeof(Instack));
	top = Time = taj = 0;
	for(int i=1;i<=all;i++)if(DFN[i]==-1 )tarjan(i, i); //注意开始点标!!!
}
vector<int>G[N];
int du[N];
void suodian(){
	memset(du, 0, sizeof(du));
	for(int i = 1; i <= taj; i++)G[i].clear();
	for(int i = 0; i < edgenum; i++){
		int u = Belong[edge[i].from], v = Belong[edge[i].to];
		if(u!=v)G[u].push_back(v), du[v]++;
	}
}
void init(){memset(head, -1, sizeof(head)); edgenum=0;}

int main()
{
	int u,v,i;
	while(scanf("%d%d",&n,&m)!=EOF)
	{
		if(n==0&&m==0) break;
		init();
		for(i=1;i<=m;i++)
		{
			scanf("%d%d",&u,&v);
			add(u,v);
		}
		tarjan_init(n);
		//suodian();
		if(taj==1)
			printf("Yes\n");
		else
			printf("No\n");
	}
	return 0;
 } 


tarjan算法拓展:求强连通缩点(以下分析部分来自http://blog.csdn.net/zyy173533832/article/details/12656143?utm_source=tuicool

在学习了tarjan算法求解强连通分量之后就接触到 强连通缩点 ,但是就是不知道怎么运用tarjan算法来找缩点,后来接触了几个有关缩点的题目,才了解到缩点的关键所在;

对于一个图,我们进行强连通分量求解之后,进行缩点操作, 缩点的最大好处在于把一个杂乱无章的有向图变成一个有向无环图, 而在有向无环图中,有两种点比较特殊:一种是入度为 0 的点,另一种是 出度为 0 的点。我们把入度为0的点就叫做根,出度为0的点叫做叶子,可以参见下图

 对于图中的绿色点就是叶子,红色的点就是根,未标记的就是中间点,但是注意:这里是缩点之后形成的有向无环图,图中的每一个点就是一个强连通分量, 那么我们怎么把一个图缩点成为一个有向无环图呢 ?那么这里就用到一个数组来“ 染色 ”,我们在求强连通分量的时候有一个 Bcnt 用来记录强连通分量个数,有一个数组Belong[MAX]来染色,Belong[i] =Bcnt 就表示 i 这个元素属于第Bcnt个强连通分量,在把所有的点求完强连通分量之后,我们只是得到一个所有点的归属数组Belong[MAX],那么,之后我们就用两个数组 in[MAX],out[MAX]表示缩点后的有向无环图的点的入度和出度,用以下代码求解缩点形成的图 

memset(in,0,sizeof(in));
memset(out,0,sizeof(out));
for(i = 1; i <= n; i ++)
{
  for(int k = head[i]; k != -1; k = edge[k].next)
  {
    j = edge[k].to;
    if(Belong[i] != Belong[j])//注意这里就是所属的强连通分量,也就是属于哪一个缩点
      out[Belong[i]] ++, in[Belong[j]] ++;
  }
}
那么我们就得到这个有向无环图的关系,那么下面给出两个有关缩点的题目:


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

Proving Equivalences

Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 4304    Accepted Submission(s): 1527


Problem Description
Consider the following exercise, found in a generic linear algebra textbook.

Let A be an n × n matrix. Prove that the following statements are equivalent:

1. A is invertible.
2. Ax = b has exactly one solution for every n × 1 matrix b.
3. Ax = b is consistent for every n × 1 matrix b.
4. Ax = 0 has only the trivial solution x = 0. 

The typical way to solve such an exercise is to show a series of implications. For instance, one can proceed by showing that (a) implies (b), that (b) implies (c), that (c) implies (d), and finally that (d) implies (a). These four implications show that the four statements are equivalent.

Another way would be to show that (a) is equivalent to (b) (by proving that (a) implies (b) and that (b) implies (a)), that (b) is equivalent to (c), and that (c) is equivalent to (d). However, this way requires proving six implications, which is clearly a lot more work than just proving four implications!

I have been given some similar tasks, and have already started proving some implications. Now I wonder, how many more implications do I have to prove? Can you help me determine this?
 

Input
On the first line one positive number: the number of testcases, at most 100. After that per testcase:

* One line containing two integers n (1 ≤ n ≤ 20000) and m (0 ≤ m ≤ 50000): the number of statements and the number of implications that have already been proved.
* m lines with two integers s1 and s2 (1 ≤ s1, s2 ≤ n and s1 ≠ s2) each, indicating that it has been proved that statement s1 implies statement s2.
 

Output
Per testcase:

* One line with the minimum number of additional implications that need to be proved in order to prove that all statements are equivalent.
 

Sample Input
  
  
2 4 0 3 2 1 2 1 3
 

Sample Output
  
  
4 2
 

Source
 

题意:这里有一个东西要你证明,就是有n个式子,用1到n标记,有m个关系,每个关系为a b 表示a推导出b,那么我们要这n个式子都是等价的最少还需要多少个关系。

编程思想: 至少加几条边让整个图变成强连通。这里我们把n个式子看做n个点,那么要是n个点等价就是任意一个点能推导出任意的另一个点,意思就是最后要是一个强连通,问最少要添加多少条边,我们根据已有的关系建图之后,强连通缩点,然后我们分别求叶子和根的数量,那么最多的那个就是我们要的答案,但是当缩点只有一个点的时候,答案是 0。

AC code:

#include <iostream>
#include <cmath>
#include<stdlib.h> 
#include<vector>
#include<cstring>
using namespace std;
#define N 30100
//N为最大点数
#define M 150100
//M为最大边数
int n, m;//n m 为点数和边数

struct Edge{
	int from, to, nex;
	bool sign;//是否为桥
}edge[M<<1];
int head[N], edgenum;
void add(int u, int v){//边的起点和终点
	Edge E={u, v, head[u], false};
	edge[edgenum] = E;
	head[u] = edgenum++;
}

int DFN[N], Low[N], Stack[N], top, Time; //Low[u]是点集{u点及以u点为根的子树} 中(所有反向弧)能指向的(离根最近的祖先v) 的DFN[v]值(即v点时间戳)
int taj;//连通分支标号,从1开始
int Belong[N];//Belong[i] 表示i点属于的连通分支
bool Instack[N];
vector<int> bcc[N]; //标号从1开始
int in[N*2],out[N*2];//记录缩点后的入度出度

void tarjan(int u ,int fa){  
	DFN[u] = Low[u] = ++ Time ;  
	Stack[top ++ ] = u ;  
	Instack[u] = 1 ;  

	for (int i = head[u] ; ~i ; i = edge[i].nex ){  
		int v = edge[i].to ;  
		if(DFN[v] == -1)
		{  
			tarjan(v , u) ;  
			Low[u] = min(Low[u] ,Low[v]) ;
			if(DFN[u] < Low[v])
			{
				edge[i].sign = 1;//为割桥
			}
		}  
		else if(Instack[v]) Low[u] = min(Low[u] ,DFN[v]) ; 		
	}  
	if(Low[u] == DFN[u]){  
		int now;
		taj ++ ; bcc[taj].clear();
		do{
			now = Stack[-- top] ;  
			Instack[now] = 0 ; 
			Belong [now] = taj ;
			bcc[taj].push_back(now);
		}while(now != u) ;
	}
}

void tarjan_init(int all){
	memset(DFN, -1, sizeof(DFN));
	memset(Instack, 0, sizeof(Instack));
	top = Time = taj = 0;
	for(int i=1;i<=all;i++)if(DFN[i]==-1 )tarjan(i, i); //注意开始点标!!!
}
vector<int>G[N];//求解缩点形成的有向无环图 
int du[N];//入度 
void suodian(){
	memset(du, 0, sizeof(du));
	for(int i = 1; i <= taj; i++)G[i].clear();
	for(int i = 0; i < edgenum; i++){
		int u = Belong[edge[i].from], v = Belong[edge[i].to];//注意这里就是所属的强连通分量,也就是属于哪一个缩点
		if(u!=v)G[u].push_back(v), du[v]++;//入度++ 
	}
}
void init(){memset(head, -1, sizeof(head)); edgenum=0;}

int main()
{
	int u,v,i,j,T;
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d%d",&n,&m);
		init();
		for(i=1;i<=m;i++)
		{
			scanf("%d%d",&u,&v);
			add(u,v);
		}
		tarjan_init(n);
		//suodian();
		/*根据已有的关系建图之后,强连通缩点,然后我们分别求叶子和根的数量,
		那么最多的那个就是我们要的答案,但是当缩点只有一个点的时候,答案是 0*/ 
		if(taj==1)
			printf("0\n");
		else
		{
			memset(in,0,sizeof(in));
		    memset(out,0,sizeof(out));
		    for(i = 1; i <= n; i ++)
		    {
		      for(int k = head[i]; k != -1; k = edge[k].nex)
		      {
		        j = edge[k].to;
		        if(Belong[i] != Belong[j])
		          out[Belong[i]] ++, in[Belong[j]] ++;
		      }
		    }
		    //缩点之后找叶子和根的数量
		    int root = 0,leaf = 0;
		    for(i = 1; i <= taj; i ++)
		    {
		      if(in[i] == 0) root ++;
		      if(out[i] == 0) leaf ++;
		    }
		    printf("%d\n",leaf > root ? leaf : root);
		}
	}
	return 0;
 } 


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


Popular Cows
Time Limit: 2000MS Memory Limit: 65536K
Total Submissions: 27757 Accepted: 11177

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. 

Source



题意:有N头牛,M个关系,每个关系为 a b,表示牛a认为牛b 收欢迎,那么问根据所给信息判断有多少头牛是收到所有的牛的欢迎,而且这里a认为b受欢迎,b认为c受欢迎,那么a也会认为c受欢迎。


编程思想:这里我们根据M个关系建图,然后对于这个图缩点,那么,根据上面的缩点分析,我们只要求叶子的个数,因为别的缩点都指向叶子,叶子处在最高层,就是最受欢迎的,那么我们根据所有缩点的out[i]=0的个数,判断,如果只有一个,那么这个缩点(强连通分量)的牛都是最受欢迎的,(这里注意,叶子数为0就是表示只有一个强连通分量,那么所有的牛都是最受欢迎的),如果有多个就输出0。

#include <iostream>
#include <cmath>
#include<stdlib.h> 
#include<vector>
#include<cstring>
#include<stdio.h>
using namespace std;
#define N 30100
//N为最大点数
#define M 150100
//M为最大边数
int n, m;//n m 为点数和边数

struct Edge{
	int from, to, nex;
	bool sign;//是否为桥
}edge[M<<1];
int head[N], edgenum;
void add(int u, int v){//边的起点和终点
	Edge E={u, v, head[u], false};
	edge[edgenum] = E;
	head[u] = edgenum++;
}

int DFN[N], Low[N], Stack[N], top, Time; //Low[u]是点集{u点及以u点为根的子树} 中(所有反向弧)能指向的(离根最近的祖先v) 的DFN[v]值(即v点时间戳)
int taj;//连通分支标号,从1开始
int Belong[N];//Belong[i] 表示i点属于的连通分支
bool Instack[N];
vector<int> bcc[N]; //标号从1开始
int in[N*2],out[N*2];//记录缩点后的入度出度

void tarjan(int u ,int fa){  
	DFN[u] = Low[u] = ++ Time ;  
	Stack[top ++ ] = u ;  
	Instack[u] = 1 ;  

	for (int i = head[u] ; ~i ; i = edge[i].nex ){  
		int v = edge[i].to ;  
		if(DFN[v] == -1)
		{  
			tarjan(v , u) ;  
			Low[u] = min(Low[u] ,Low[v]) ;
			if(DFN[u] < Low[v])
			{
				edge[i].sign = 1;//为割桥
			}
		}  
		else if(Instack[v]) Low[u] = min(Low[u] ,DFN[v]) ; 		
	}  
	if(Low[u] == DFN[u]){  
		int now;
		taj ++ ; bcc[taj].clear();
		do{
			now = Stack[-- top] ;  
			Instack[now] = 0 ; 
			Belong [now] = taj ;
			bcc[taj].push_back(now);
		}while(now != u) ;
	}
}

void tarjan_init(int all){
	memset(DFN, -1, sizeof(DFN));
	memset(Instack, 0, sizeof(Instack));
	top = Time = taj = 0;
	for(int i=1;i<=all;i++)if(DFN[i]==-1 )tarjan(i, i); //注意开始点标!!!
}
vector<int>G[N];//求解缩点形成的有向无环图 
int du[N];//入度 
void suodian(){
	memset(du, 0, sizeof(du));
	for(int i = 1; i <= taj; i++)G[i].clear();
	for(int i = 0; i < edgenum; i++){
		int u = Belong[edge[i].from], v = Belong[edge[i].to];//注意这里就是所属的强连通分量,也就是属于哪一个缩点
		if(u!=v)G[u].push_back(v), du[v]++;//入度++ 
	}
}
void init(){memset(head, -1, sizeof(head)); edgenum=0;}

int main()
{
	int u,v,i,j,T;
	scanf("%d%d",&n,&m);
	init();
	for(i=1;i<=m;i++)
	{
		scanf("%d%d",&u,&v);
		add(u,v);
	}
	tarjan_init(n);
	//suodian();
	/*根据已有的关系建图之后,强连通缩点,然后我们分别求叶子和根的数量,
	那么最多的那个就是我们要的答案,但是当缩点只有一个点的时候,答案是 0*/ 
	if(taj==1)
		printf("%d\n",n);
	else
	{
		memset(in,0,sizeof(in));
	    memset(out,0,sizeof(out));
	    for(i = 1; i <= n; i ++)
	    {
	      for(int k = head[i]; k != -1; k = edge[k].nex)
	      {
	        j = edge[k].to;
	        if(Belong[i] != Belong[j])
	          out[Belong[i]] ++, in[Belong[j]] ++;
	      }
	    }
	    //缩点之后找叶子和根的数量
	    int root = 0,leaf = 0,leaf_num,ans=0;
	    for(i = 1; i <= taj; i ++)
	    {
	      if(in[i] == 0) root ++;
	      if(out[i] == 0) leaf ++,leaf_num=i;
	    }
	    if(leaf==1)
	    {
	    	for(i=1;i<=n;i++)
	    	{
	    		if(Belong[i]==leaf_num)
	    			ans++;
			}
	    	printf("%d\n",ans);
		}
		else
			printf("0\n");
	    
	}
	return 0;
 } 


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

Equivalent Sets

Time Limit: 12000/4000 MS (Java/Others)    Memory Limit: 104857/104857 K (Java/Others)
Total Submission(s): 3592    Accepted Submission(s): 1249


Problem Description
To prove two sets A and B are equivalent, we can first prove A is a subset of B, and then prove B is a subset of A, so finally we got that these two sets are equivalent.
You are to prove N sets are equivalent, using the method above: in each step you can prove a set X is a subset of another set Y, and there are also some sets that are already proven to be subsets of some other sets.
Now you want to know the minimum steps needed to get the problem proved.
 

Input
The input file contains multiple test cases, in each case, the first line contains two integers N <= 20000 and M <= 50000.
Next M lines, each line contains two integers X, Y, means set X in a subset of set Y.
 

Output
For each case, output a single integer: the minimum steps needed.
 

Sample Input
  
  
4 0 3 2 1 2 1 3
 

Sample Output
  
  
4 2
Hint
Case 2: First prove set 2 is a subset of set 1 and then prove set 3 is a subset of set 1.
 

Source
 

编程思想:至少加几条边让整个图变成强连通,与Link2类似。 

AC code:
#include <iostream>
#include <cmath>
#include<stdlib.h> 
#include<vector>
#include<cstring>
using namespace std;
#define N 30100
//N为最大点数
#define M 150100
//M为最大边数
int n, m;//n m 为点数和边数

struct Edge{
	int from, to, nex;
	bool sign;//是否为桥
}edge[M<<1];
int head[N], edgenum;
void add(int u, int v){//边的起点和终点
	Edge E={u, v, head[u], false};
	edge[edgenum] = E;
	head[u] = edgenum++;
}

int DFN[N], Low[N], Stack[N], top, Time; //Low[u]是点集{u点及以u点为根的子树} 中(所有反向弧)能指向的(离根最近的祖先v) 的DFN[v]值(即v点时间戳)
int taj;//连通分支标号,从1开始
int Belong[N];//Belong[i] 表示i点属于的连通分支
bool Instack[N];
vector<int> bcc[N]; //标号从1开始
int in[N*2],out[N*2];//记录缩点后的入度出度

void tarjan(int u ,int fa){  
	DFN[u] = Low[u] = ++ Time ;  
	Stack[top ++ ] = u ;  
	Instack[u] = 1 ;  

	for (int i = head[u] ; ~i ; i = edge[i].nex ){  
		int v = edge[i].to ;  
		if(DFN[v] == -1)
		{  
			tarjan(v , u) ;  
			Low[u] = min(Low[u] ,Low[v]) ;
			if(DFN[u] < Low[v])
			{
				edge[i].sign = 1;//为割桥
			}
		}  
		else if(Instack[v]) Low[u] = min(Low[u] ,DFN[v]) ; 		
	}  
	if(Low[u] == DFN[u]){  
		int now;
		taj ++ ; bcc[taj].clear();
		do{
			now = Stack[-- top] ;  
			Instack[now] = 0 ; 
			Belong [now] = taj ;
			bcc[taj].push_back(now);
		}while(now != u) ;
	}
}

void tarjan_init(int all){
	memset(DFN, -1, sizeof(DFN));
	memset(Instack, 0, sizeof(Instack));
	top = Time = taj = 0;
	for(int i=1;i<=all;i++)if(DFN[i]==-1 )tarjan(i, i); //注意开始点标!!!
}
vector<int>G[N];//求解缩点形成的有向无环图 
int du[N];//入度 
void suodian(){
	memset(du, 0, sizeof(du));
	for(int i = 1; i <= taj; i++)G[i].clear();
	for(int i = 0; i < edgenum; i++){
		int u = Belong[edge[i].from], v = Belong[edge[i].to];//注意这里就是所属的强连通分量,也就是属于哪一个缩点
		if(u!=v)G[u].push_back(v), du[v]++;//入度++ 
	}
}
void init(){memset(head, -1, sizeof(head)); edgenum=0;}

int main()
{
	int u,v,i,j,T;
	while(scanf("%d%d",&n,&m)!=EOF)
	{
		init();
		for(i=1;i<=m;i++)
		{
			scanf("%d%d",&u,&v);
			add(u,v);
		}
		tarjan_init(n);
		//suodian();
		/*根据已有的关系建图之后,强连通缩点,然后我们分别求叶子和根的数量,
		那么最多的那个就是我们要的答案,但是当缩点只有一个点的时候,答案是 0*/ 
		if(taj==1)
			printf("0\n");
		else
		{
			memset(in,0,sizeof(in));
		    memset(out,0,sizeof(out));
		    for(i = 1; i <= n; i ++)
		    {
		      for(int k = head[i]; k != -1; k = edge[k].nex)
		      {
		        j = edge[k].to;
		        if(Belong[i] != Belong[j])
		          out[Belong[i]] ++, in[Belong[j]] ++;
		      }
		    }
		    //缩点之后找叶子和根的数量
		    int root = 0,leaf = 0;
		    for(i = 1; i <= taj; i ++)
		    {
		      if(in[i] == 0) root ++;
		      if(out[i] == 0) leaf ++;
		    }
		    printf("%d\n",leaf > root ? leaf : root);
		}
	}
	return 0;
 } 


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


Summer Holiday

Time Limit: 10000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 2295    Accepted Submission(s): 1067


Problem Description
To see a World in a Grain of Sand 
And a Heaven in a Wild Flower, 
Hold Infinity in the palm of your hand 
And Eternity in an hour. 
                  —— William Blake

听说lcy帮大家预定了新马泰7日游,Wiskey真是高兴的夜不能寐啊,他想着得快点把这消息告诉大家,虽然他手上有所有人的联系方式,但是一个一个联系过去实在太耗时间和电话费了。他知道其他人也有一些别人的联系方式,这样他可以通知其他人,再让其他人帮忙通知一下别人。你能帮Wiskey计算出至少要通知多少人,至少得花多少电话费就能让所有人都被通知到吗?
 

Input
多组测试数组,以EOF结束。
第一行两个整数N和M(1<=N<=1000, 1<=M<=2000),表示人数和联系对数。
接下一行有N个整数,表示Wiskey联系第i个人的电话费用。
接着有M行,每行有两个整数X,Y,表示X能联系到Y,但是不表示Y也能联系X。
 

Output
输出最小联系人数和最小花费。
每个CASE输出答案一行。
 

Sample Input
  
  
12 16 2 2 2 2 2 2 2 2 2 2 2 2 1 3 3 2 2 1 3 4 2 4 3 5 5 4 4 6 6 4 7 4 7 12 7 8 8 7 8 9 10 9 11 10
 

Sample Output
  
  
3 6
 

Author
威士忌
 

Source
 


编程思想: 传递的最小费用。由题意,可抽象出:先用tarjan算法求强连通分量,然后由缩点构造有向无环图(树),找出其中入度为0的缩点(根)个数即为最小联系人数,然后对每一个入度为0的缩点,取其所包含的原来的结点的最小花费,就是 Wiskey要联系该缩点的最小花费,而全部入度为0的缩点(根)的最小花费和即为总的最小花费。简而言之,该题就是求出所有无入边的连通块的最小权,这些最小权的和即答案。


AC code:

#include <iostream>
#include <cmath>
#include<stdlib.h> 
#include<vector>
#include<cstring>
#include<stdio.h>
#include<algorithm>
#define LL long long
using namespace std;
#define INF 0x3f3f3f3f
#define N 30100
//N为最大点数
#define M 150100
//M为最大边数
int n, m;//n m 为点数和边数

struct Edge{
	int from, to, nex;
	bool sign;//是否为桥
}edge[M<<1];
int head[N], edgenum;
void add(int u, int v){//边的起点和终点
	Edge E={u, v, head[u], false};
	edge[edgenum] = E;
	head[u] = edgenum++;
}

int DFN[N], Low[N], Stack[N], top, Time; //Low[u]是点集{u点及以u点为根的子树} 中(所有反向弧)能指向的(离根最近的祖先v) 的DFN[v]值(即v点时间戳)
int taj;//连通分支标号,从1开始
int Belong[N];//Belong[i] 表示i点属于的连通分支
bool Instack[N];
vector<int> bcc[N]; //标号从1开始
int in[N*2],out[N*2];//记录缩点后的入度出度

void tarjan(int u ,int fa){  
	DFN[u] = Low[u] = ++ Time ;  
	Stack[top ++ ] = u ;  
	Instack[u] = 1 ;  

	for (int i = head[u] ; ~i ; i = edge[i].nex ){  
		int v = edge[i].to ;  
		if(DFN[v] == -1)
		{  
			tarjan(v , u) ;  
			Low[u] = min(Low[u] ,Low[v]) ;
			if(DFN[u] < Low[v])
			{
				edge[i].sign = 1;//为割桥
			}
		}  
		else if(Instack[v]) Low[u] = min(Low[u] ,DFN[v]) ; 		
	}  
	if(Low[u] == DFN[u]){  
		int now;
		taj ++ ; bcc[taj].clear();
		do{
			now = Stack[-- top] ;  
			Instack[now] = 0 ; 
			Belong [now] = taj ;
			bcc[taj].push_back(now);
		}while(now != u) ;
	}
}

void tarjan_init(int all){
	memset(DFN, -1, sizeof(DFN));
	memset(Instack, 0, sizeof(Instack));
	top = Time = taj = 0;
	for(int i=1;i<=all;i++)if(DFN[i]==-1 )tarjan(i, i); //注意开始点标!!!
}
vector<int>G[N];//求解缩点形成的有向无环图 
int du[N];//入度 
void suodian(){
	memset(du, 0, sizeof(du));
	for(int i = 1; i <= taj; i++)G[i].clear();
	for(int i = 0; i < edgenum; i++){
		int u = Belong[edge[i].from], v = Belong[edge[i].to];//注意这里就是所属的强连通分量,也就是属于哪一个缩点
		if(u!=v)G[u].push_back(v), du[v]++;//入度++ 
	}
}
void init(){memset(head, -1, sizeof(head)); edgenum=0;}
int cost[1010],w[1010]; 

int main()
{
	//freopen("D:\\in.txt","r",stdin);
	int u,v,i,j,T;
	while(scanf("%d%d",&n,&m)!=EOF)
	{
		init();
		for(i=1;i<=n;i++)
		{
			scanf("%d",&w[i]);
		}
		for(i=1;i<=m;i++)
		{
			scanf("%d%d",&u,&v);
			add(u,v);
		}
		tarjan_init(n);
		memset(in,0,sizeof(in));
	    memset(out,0,sizeof(out));
	    for(i = 1; i <= n; i ++)
	    {
	      for(int k = head[i]; k != -1; k = edge[k].nex)
	      {
	        j = edge[k].to;
	        if(Belong[i] != Belong[j])
	          out[Belong[i]] ++, in[Belong[j]] ++;
	      }
	    }
	    LL ans=0;
	    //缩点之后找叶子和根的数量
	    int root = 0,leaf = 0;
	    for(i = 1; i <= taj; i ++)
	    {
	      if(in[i] == 0) 
		  {
		  	cost[i]=INF;
		  	root ++;
		  	for(j=1;j<=n;j++)
		  	{
		  		if(Belong[j]==i)
		  			cost[i]=min(cost[i],w[j]);
			}
			ans+=cost[i];
		  }
	      if(out[i] == 0) leaf ++;
	    }
	    printf("%d %I64d\n",root,ans);
	}
	return 0;
 } 


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

Intelligence System

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 1942    Accepted Submission(s): 839


Problem Description
After a day, ALPCs finally complete their ultimate intelligence system, the purpose of it is of course for ACM ... ... 
Now, kzc_tc, the head of the Intelligence Department (his code is once 48, but now 0), is sudden obtaining important information from one Intelligence personnel. That relates to the strategic direction and future development of the situation of ALPC. So it need for emergency notification to all Intelligence personnel, he decides to use the intelligence system (kzc_tc inform one, and the one inform other one or more, and so on. Finally the information is known to all).
We know this is a dangerous work. Each transmission of the information can only be made through a fixed approach, from a fixed person to another fixed, and cannot be exchanged, but between two persons may have more than one way for transferring. Each act of the transmission cost Ci (1 <= Ci <= 100000), the total cost of the transmission if inform some ones in our ALPC intelligence agency is their costs sum. 
Something good, if two people can inform each other, directly or indirectly through someone else, then they belong to the same branch (kzc_tc is in one branch, too!). This case, it’s very easy to inform each other, so that the cost between persons in the same branch will be ignored. The number of branch in intelligence agency is no more than one hundred.
As a result of the current tensions of ALPC’s funds, kzc_tc now has all relationships in his Intelligence system, and he want to write a program to achieve the minimum cost to ensure that everyone knows this intelligence.
It's really annoying!
 

Input
There are several test cases. 
In each case, the first line is an Integer N (0< N <= 50000), the number of the intelligence personnel including kzc_tc. Their code is numbered from 0 to N-1. And then M (0<= M <= 100000), the number of the transmission approach.
The next M lines, each line contains three integers, X, Y and C means person X transfer information to person Y cost C. 
 

Output
The minimum total cost for inform everyone.
Believe kzc_tc’s working! There always is a way for him to communicate with all other intelligence personnel.
 

Sample Input
  
  
3 3 0 1 100 1 2 50 0 2 100 3 3 0 1 100 1 2 50 2 1 100 2 2 0 1 50 0 1 100
 

Sample Output
  
  
150 100 50
 

Source
 


题意:给了一个含有 n(0<n<=50000) 个节点的有向图,图中的两点之间的通信时要付出代价的(经过的边权之和),但是如果这两个点之间相互可达,代价为 0
问,从给定的节点向其他所有的点通信,所花费的最小代价是多少?


编程思想:传递的最小费用。先对原图缩点,形成一个 DAG,给的那个定点显然是 DAG 中入度为 0 的点,并且入度为 0 的点肯定只有一个(根据题目的意思)每个顶点(除了那个定点)必定只有一个入点,那么,对于每个顶点,完全可以选择代价最小的那条入点,贪心的找即可。


AC code:

#include <iostream>
#include <cmath>
#include<stdlib.h> 
#include<vector>
#include<cstring>
#include<stdio.h>
#include<algorithm>
#define LL long long
using namespace std;
#define INF 0x7fffffff
#define N 50100
//N为最大点数
#define M 150100
//M为最大边数
int n, m;//n m 为点数和边数

struct Edge{
	int from, to, nex,w;
	bool sign;//是否为桥
}edge[M<<1];
int head[N], edgenum;
void add(int u, int v,int w){//边的起点和终点
	Edge E={u, v, head[u], w,false};
	edge[edgenum] = E;
	head[u] = edgenum++;
}

int DFN[N], Low[N], Stack[N], top, Time; //Low[u]是点集{u点及以u点为根的子树} 中(所有反向弧)能指向的(离根最近的祖先v) 的DFN[v]值(即v点时间戳)
int taj;//连通分支标号,从1开始
int Belong[N];//Belong[i] 表示i点属于的连通分支
bool Instack[N];
vector<int> bcc[N]; //标号从1开始
int in[N*2],out[N*2];//记录缩点后的入度出度

void tarjan(int u ,int fa){  
	DFN[u] = Low[u] = ++ Time ;  
	Stack[top ++ ] = u ;  
	Instack[u] = 1 ;  

	for (int i = head[u] ; ~i ; i = edge[i].nex ){  
		int v = edge[i].to ;  
		if(DFN[v] == -1)
		{  
			tarjan(v , u) ;  
			Low[u] = min(Low[u] ,Low[v]) ;
			if(DFN[u] < Low[v])
			{
				edge[i].sign = 1;//为割桥
			}
		}  
		else if(Instack[v]) Low[u] = min(Low[u] ,DFN[v]) ; 		
	}  
	if(Low[u] == DFN[u]){  
		int now;
		taj ++ ; bcc[taj].clear();
		do{
			now = Stack[-- top] ;  
			Instack[now] = 0 ; 
			Belong [now] = taj ;
			bcc[taj].push_back(now);
		}while(now != u) ;
	}
}

void tarjan_init(int all){
	memset(DFN, -1, sizeof(DFN));
	memset(Instack, 0, sizeof(Instack));
	top = Time = taj = 0;
	for(int i=1;i<=all;i++)if(DFN[i]==-1 )tarjan(i, i); //注意开始点标!!!
}
vector<int>G[N];//求解缩点形成的有向无环图 
int du[N];//入度 
int cost[N],w[N]; 
void suodian(){
	memset(du, 0, sizeof(du));
	for(int i = 1; i <= taj; i++) G[i].clear(),w[i]=INF;
	for(int i = 0; i < edgenum; i++){
		int u = Belong[edge[i].from], v = Belong[edge[i].to];//注意这里就是所属的强连通分量,也就是属于哪一个缩点
		if(u!=v)G[u].push_back(v), du[v]++,w[v]=min(w[v],edge[i].w);//入度++,强连通模板(缩点),寻找每个点的从其他点到该点的最小权值的边 
	}
}
void init(){memset(head, -1, sizeof(head)); edgenum=0;}


int main()
{
//	freopen("D:\\in.txt","r",stdin);
	int u,v,ww,i,j,k,T;
	while(scanf("%d%d",&n,&m)!=EOF)
	{
		init();
		for(i=1;i<=m;i++)
		{
			scanf("%d%d%d",&u,&v,&ww);
			add(u+1,v+1,ww);
		}
		tarjan_init(n);
		suodian();
	    LL ans=0;
	    for(i = 1; i <= taj; i ++)
	    {
	    	if(w[i]!=INF)//w[i]==INF则说明缩点i为根节点(入度为0),且入度为 0 的点肯定只有一个(根据题目的意思),将其排除后, 
			            //对于每个顶点,完全可以选择代价最小的那条入点。
				ans+=w[i];//强连通缩点后,内部的值无效,求缩点后的有向无环图的,到达所有点的最短距离和  
		}
	    printf("%I64d\n",ans);
	}
	return 0;
 } 


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

林下的码路

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值