先给出模板:(注:模板参考自九野的博客)
时间复杂度为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
3 3 1 2 2 3 3 1 3 3 1 2 2 3 3 2 0 0
Yes No
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
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?
* 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.
* One line with the minimum number of additional implications that need to be proved in order to prove that all statements are equivalent.
2 4 0 3 2 1 2 1 3
4 2
#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
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
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.
Next M lines, each line contains two integers X, Y, means set X in a subset of set Y.
4 0 3 2 1 2 1 3
4 2HintCase 2: First prove set 2 is a subset of set 1 and then prove set 3 is a subset of set 1.
#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
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计算出至少要通知多少人,至少得花多少电话费就能让所有人都被通知到吗?
第一行两个整数N和M(1<=N<=1000, 1<=M<=2000),表示人数和联系对数。
接下一行有N个整数,表示Wiskey联系第i个人的电话费用。
接着有M行,每行有两个整数X,Y,表示X能联系到Y,但是不表示Y也能联系X。
每个CASE输出答案一行。
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
3 6
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
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!
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.
Believe kzc_tc’s working! There always is a way for him to communicate with all other intelligence personnel.
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
150 100 50
问,从给定的节点向其他所有的点通信,所花费的最小代价是多少?
编程思想:传递的最小费用。先对原图缩点,形成一个 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;
}