强联通是有向图G中u和v两个点可以互相到达,则u和v即为强联通。
强联通分量是这一部分联通块的所有点两两相互强联通,即SCC。
求SCC
Kosaraju算法
利用补图
const int MAX_N=10010;
vector<int>v[MAX_N],rv[MAX_N];
vector<int>s;
int vis[MAX_N],rvis[MAX_N],ans;//ans即为强连通分量的个数
void dfs1(int now){
int i;
if(vis[now])
return;
vis[now]=1;
for(i=0;i<v[now].size();i++)
s.push_back(now);
}
void dfs2(int now){
int i;
if(rvis[now])
returnl
for(i=0;i<rv[now].size();i++)
dfs2(rv[now][i]);
}
void kisaraju(int now){
cnt=0;
s.clear();
memset(vis,0,sizeof(vis));
memset(rvis,0,sizeof(rvis));
for(i=1;i<=n;i++)
dfs1(i);
for(i=n-1;i>0;i--)
if(!rvis[i]){
ans++;
dfs2(s[i]);
}
}
Tarjan算法,只做一次dfs比kosaraju快
const int MAX_N=101000;
int ans;//强连通分量的个数
int low[MAX_N],num[MAX_N],cnt;
int stack[MAX_N],top;
int vis[MAX_N];
vector<int>v[MAX_N];
void dfs(int now){
int i;
stack[top++]=now;
low[now]=num[now]=++cnt;
for(i=0;i<v[now].size();i++){
int to=v[now][i];
if(!num[to]){
dfs(to);
low[now]=min(low[now],low[to]);
}
else if(!vis[to])
low[now]=min(low[now],num[to]);
}
if(low[now]==num[now]){
ans++;
while(1){
int to=stack[--top];
vis[to]=ans;
if(now==to)
break;
}
}
}
void tarjan(int n){
int i;
ans=cnt=top=0;
memset(vis,0,sizeof(vis));
memset(num,0,sizeof(num));
memset(low,0,sizeof(low));
for(i=1;i<=n;i++){
if(!num[i])
dfs(i);
}
}
#include<iostream>
#include<cstdio>
#include<vector>
#include<cstring>
using namespace std;
const int MAX_N=1010;
const int INF=0x3f3f3f3f;
int ans;//强连通分量的个数
int low[MAX_N],num[MAX_N],cnt,sum[MAX_N];
int money[MAX_N],pay[MAX_N];
int stack[MAX_N],top;
int vis[MAX_N];
vector<int>v[MAX_N];
void dfs(int now){
int i;
stack[top++]=now;
low[now]=num[now]=++cnt;
for(i=0;i<v[now].size();i++){
int to=v[now][i];
if(!num[to]){
dfs(to);
low[now]=min(low[now],low[to]);
}
else if(!vis[to])
low[now]=min(low[now],num[to]);
}
if(low[now]==num[now]){
ans++;
while(1){
int to=stack[--top];
vis[to]=ans;
pay[ans]=min(pay[ans],money[to]);
if(now==to)
break;
}
}
}
void tarjan(int n){
int i,j;
ans=cnt=top=0;
memset(vis,0,sizeof(vis));
memset(num,0,sizeof(num));
memset(low,0,sizeof(low));
memset(sum,0,sizeof(sum));
memset(pay,0x3f,sizeof(pay));
for(i=1;i<=n;i++){
if(!num[i])
dfs(i);
}
for(i=1;i<=n;i++){
for(j=0;j<v[i].size();j++){
int to=v[i][j];
if(vis[i]!=vis[to]){//这里换成low[i]!=low[to]就错了,不知道为啥
sum[vis[to]]++;
}
}
}
}
int main(void){
int n,m,a,b,i;
while(scanf("%d%d",&n,&m)!=EOF){
for(i=1;i<=n;i++){
scanf("%d",&money[i]);
v[i].clear();
}
for(i=0;i<m;i++){
scanf("%d%d",&a,&b);
v[a].push_back(b);
}
tarjan(n);
int ans1=0,ans2=0;
for(i=1;i<=ans;i++){
if(sum[i]==0){
ans1++;
ans2+=pay[i];
}
}
printf("%d %d\n",ans1,ans2);
}
return 0;
}
HDU 3836 Equivalent Sets
给定有向图,至少要添加多少条边才能成为强连通图。
缩点讨论,ans=max(入度为0的缩点个数,出度为0的缩点个数),当只有一个联通块时特判即可。
注意缩点的标号为vis[i]!!!
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
const int MAX_N=21000;
int ans;//强连通分量的个数
int low[MAX_N],num[MAX_N],cnt;
int in[MAX_N],out[MAX_N];
int stack[MAX_N],top;
int vis[MAX_N];
vector<int>v[MAX_N];
void dfs(int now){
int i;
stack[top++]=now;
low[now]=num[now]=++cnt;
for(i=0;i<v[now].size();i++){
int to=v[now][i];
if(!num[to]){
dfs(to);
low[now]=min(low[now],low[to]);
}
else if(!vis[to])
low[now]=min(low[now],num[to]);
}
if(low[now]==num[now]){
ans++;
while(1){
int to=stack[--top];
vis[to]=ans;
if(now==to)
break;
}
}
}
void tarjan(int n){
int i,j;
ans=cnt=top=0;
for(i=1;i<=n;i++){
if(!num[i])
dfs(i);
}
for(i=1;i<=n;i++){
for(j=0;j<v[i].size();j++){
int to=v[i][j];
if(vis[i]!=vis[to]){
out[vis[i]]++;
in[vis[to]]++;
}
}
}
}
int main(void){
int n,m,a,b,i;
while(scanf("%d%d",&n,&m)!=EOF){
for(i=1;i<=n;i++){
in[i]=0;
out[i]=0;
low[i]=0;
num[i]=0;
vis[i]=0;
v[i].clear();
}
for(i=0;i<m;i++){
scanf("%d%d",&a,&b);
v[a].push_back(b);
}
tarjan(n);
if(ans==1){
printf("0\n");
continue;
}
int x=0,y=0;
for(i=1;i<=ans;i++){
if(!in[i])
x++;
if(!out[i])
y++;
}
int xans=max(x,y);
printf("%d\n",xans);
}
return 0;
}