HDU2767
题目的意思很简单,给定你一个有向图,问最少加几条边可以使得整个图变为强连通图。
[任意两点之间连通]
思路:
1.先跑一次Tarjan将这个有向图分割成不同的连通子图。连通子图的数目用color标记,即我们用染色的方式区分不同的连通子图。
2.Tarjan算法中,对color进行更新时对连通分量缩点。所谓的缩点,其实就是给每个节点做一个标记,标记该点属于哪个连通分量,然后整个连通分量就相当于一个点了。我们创建belong数组,其中belong[v] = color表示节点v被染成了color色(也就是说v点属于color这个连通分量)。
3.通过缩点,整个有向图可以看做是一个有向无环图(DAG),不难想象,任何一个DAG都有至少一个入度为0的节点v1,也至少存在一个出度为0的节点v2。然后,我们记录每一个连通分量的所有出度为0或者入度为0的节点的数目。
4.最后记录所有连通分量的入度为0的数目cnt1和出度为0的数目cnt2,cnt1和cnt2两者之间的最大值就是我们要求的添加最少的边的数目。
[原理我也并不清楚…]
代码:
/*HDU 2767
Judge Status : Accepted
Time: 296MS
不知道为什么如果stack<int> s放在局部函数Tarjan里边就会Runtime Error(STACK_OVERFLOW)
*/
#include <iostream>
#include <cstdio>
#include <vector>
#include <stack>
#include <cstring>
using namespace std;
const int maxn = 20005;
vector<int> edge[maxn];
stack<int> s;//为什么要放在外边啊!
int dfn[maxn],low[maxn],belong[maxn];
int indegree[maxn],outdegree[maxn];
bool instack[maxn];
int time,color;
int Min(int x,int y) { return (x < y) ? x : y ;}
int Max(int x,int y) { return (x > y) ? x : y ;}
void init(int n)
{
time = 0;//时间戳
color = 0;//强连通分量的个数,给不同的连通分量染上不同的颜色进行区分
memset(dfn,0,sizeof(dfn));
memset(low,0,sizeof(low));
memset(instack,false,sizeof(instack));
memset(belong,0,sizeof(belong));
for(int i = 0 ; i <= n ; ++i)
edge[i].clear();
}
void tarjan(int v1)
{
int v2;
dfn[v1] = low[v1] = ++time;//赋予初始值
s.push(v1);
instack[v1] = true;
for(unsigned i = 0 ; i < edge[v1].size() ; ++i){
v2 = edge[v1][i];//与v1向连的点
if(dfn[v2] == 0){
tarjan(v2);
low[v1] = Min(low[v1],low[v2]);
}
else if(instack[v2] && dfn[v2] < low[v1])//如果要搜素的点v2已经在栈中
low[v1] = dfn[v2];
}
if(dfn[v1] == low[v1]){//找到连通分量
color++;
do{
v2 = s.top();
s.pop();
instack[v2] = false;
belong[v2] = color;//染色过程实际上就是缩点的过程
}
while(v1 != v2);
}
}
void solve(int n,int m)
{
if(color == 1){//如果只有一个联通分量,说明这是强连通图,不需要添加点
printf("0\n");
return ;
}
//统计每一个联通分量中的出入度之和
//因为每一个联通分量“缩成”了一个点,求联通分量的出入度之和实际上就是求这个“缩点”的出度和入度
for(int i = 0 ; i <= color ; ++i)
indegree[i] = outdegree[i] = 0;
for(int v1 = 1 ; v1 <= n ; ++v1){
for(unsigned i = 0 ; i < edge[v1].size() ; ++i){
int v2 = edge[v1][i];
if(belong[v1] != belong[v2]){//如果说处于不同的连通分量
outdegree[belong[v1]]++;求该联通分量的出度之和
indegree[belong[v2]]++;//求该联通分量的入度之和
}
}
}
//统计每一个“缩点”入度为0或者出度为0的数目求其最大值
int cnt1 = 0 ,cnt2 = 0;
for(int i = 1 ; i <= color ; ++i){
if(indegree[i] == 0)
cnt1++;
if(outdegree[i] == 0)
cnt2++;
}
printf("%d\n",Max(cnt1,cnt2));
}
int main()
{
int test;
int n,m;
int v1,v2;
scanf("%d",&test);
while(test--){
scanf("%d%d",&n,&m);
init(n);
for(int i = 0 ;i < m ; ++i){
scanf("%d%d",&v1,&v2);
edge[v1].push_back(v2);
}
for(int i = 1 ; i <= n ; ++i)
if(dfn[i] == 0)//如果还没有被访问过
tarjan(i);// 1 <= n <= maxn
solve(n,m);
}
return 0;
}