题意:给出n个点和m条边,问最少须添加多少个边,使得任意两点间能互通。给出的m条边是有向的。
题解:
先说结论:这是一个小题型(至少加几条边使得全图强连通),很多题目都是这样。我们只需利用tarjan求出连通块的入度为零的数量和出度为零的数量的最大值即可。
原因简析:要想使得所有点联通,则只需把各个连通块用边连起来即可,所以我们要么是所有连通块的入度均不为零,要么解决出度均不为0(其实在解决入度的的时候我们加的边相应的就解决了另一个连通块的出度,所以要取最大值)
AC代码:
#include <iostream>
#include <cstring>
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
const int maxn=1e5+5;
int n,m,low[maxn],dfn[maxn],stack[maxn],vis[maxn],cnt,ans,tot,belong[maxn],du1[maxn],du2[maxn];
vector<int>g[maxn];
void init(){
ans=0,tot=0,cnt=0;
memset(low,0,sizeof(low));
memset(dfn,0,sizeof(dfn));
memset(vis,0,sizeof(vis));
memset(du1,0,sizeof(du1));
memset(du2,0,sizeof(du2));
memset(belong,0,sizeof(belong));
for(int i=0;i<=maxn;i++)g[i].clear();
}
void tarjan(int u){
low[u]=dfn[u]=++tot;
stack[++cnt]=u;
vis[u]=1;
for(int i=0;i<g[u].size();i++){
int v=g[u][i];
if(!dfn[v]){
tarjan(v);
low[u]=min(low[v],low[u]);
}
else if(vis[v]){
low[u]=min(low[u],dfn[v]);
}
}
if(dfn[u]==low[u]){
ans++;
for(;;){
int t=stack[cnt--];
vis[t]=0;
belong[t]=ans;
if(t==u)break;
}
}
}
int main(){
while(scanf("%d%d",&n,&m)!=EOF){
init();
for(int i=1;i<=m;i++){
int x,y;
scanf("%d%d",&x,&y);
g[x].push_back(y);
}
for(int i=1;i<=n;i++){
if(!dfn[i])tarjan(i);
}
for(int i=1;i<=n;i++){//入度出度计数方法:如果不属于同一个连通块,但又存在单向边即有入度出度关系
for(int j=0;j<g[i].size();j++){
int v =g[i][j];
if(belong[i]!=belong[v]){
du1[belong[i]]++;
du2[belong[v]]++;
}
}
}
if(ans==1){//特判刚开始就符合题意得情况
printf("0\n");
continue;
}
int ans1=0,ans2=0;
for(int i=1;i<=ans;i++){//要么解决出度为零的问题,要么解决入度为零的问题,这里做统计
if(du1[i]==0)ans1++;
if(du2[i]==0)ans2++;
}
printf("%d\n",max(ans1,ans2));
}
}