题目来源:
题目大意:
现在给你一个 n 个点 m 条边的无向图,不一定联通。现在你需要把原有的无向边变为有向边,并加入一些新的有向边。问最少加入多少条有向边使得图只有一个强连通分量。
题解:
听说是个定理:可以给一个任意的边-双连通图的边定向,使它成为一个强连通图。
因为一个双连通分量按照一定的方向对边定向后,一定可以变成一个强连通分量,所以可以把图中所有的双连通分量缩成一个点。
双连通分量的缩点和强连通分量的缩点有点不同,因为割点不属于任何一个双连通分量(其实是割点会属于多个双连通分量),可以考虑使用并查集进行缩点。
缩完点后一个连通块会是一棵树,整个图是一个森林。
对一颗树边定向后添最少的边形成一个强连通图的答案是度为一的点的个数一半(向上取整)。(原因:如果本来这棵树树中的无向边就是单向的,那么它就是一个DAG,添加最少的边使得它成为强连通图,答案会是入度为0的点个数和出度为0的点个数的最大值,所以对边定向的时候要最小化这个最大值。)
对于只有一个点的连通块,答案为1。
对于一个森林答案为整个森林中度为一的点个数的一半(向上取整)。
最终答案为两者之和。特殊情况是整个图(缩完点之后的图)只有一个点,答案为0。
AC代码:
#include <bits/stdc++.h>
#define LL long long
#define LD long double
#define ULL unsigned long long
#define UI unsigned int
#define PII pair<int,int>
#define MPII(x,y) pair<int,int>{x,y}
#define _for(i,j,k) for(int i=j;i<=k;i++)
#define for_(i,j,k) for(int i=j;i>=k;i--)
#define efor(i,u) for(int i=head[u];i;i=net[i])
#define lowbit(x) (x&-x)
#define ls(x) x<<1
#define rs(x) x<<1|1
#define inf 0x3fffffff
//#pragma comment(linker, "/STACK:10240000000,10240000000")
using namespace std;
const int maxn = 2e6 + 5;
const int M = 1e9 + 7;
inline int mad(int a,int b){return (a+=b)>=M?a-M:a;}
int head[maxn],net[maxn],e[maxn],cnt;
int n,m;
void add(int u,int v){
e[++cnt]=v;
net[cnt]=head[u];
head[u]=cnt;
}
int p[maxn];
int find_p(int x){
int y,r=x;
while(r!=p[r]) r=p[r];
while(p[x]!=r){
y=p[x];
p[x]=r;
x=y;
}
return r;
}
void union_p(int x,int y){
int px=find_p(x),py=find_p(y);
if(px!=py){
p[px]=py;
}
}
int df,dfn[maxn],low[maxn],bccn[maxn],bc;
int isc[maxn];
vector<int> bcc[maxn];
stack<int> st;
void tarjan(int u,int pre){
dfn[u]=low[u]= ++df;
int child=0;
st.push(u);
for(int i=head[u];i;i=net[i]){
if(!dfn[e[i]]){
child++;
tarjan(e[i],u);
low[u]=min(low[u],low[e[i]]);
if(low[e[i]]>=dfn[u]){
isc[u]=1;
bc++;bcc[bc].clear();
bcc[bc].push_back(u);
while(bccn[e[i]]!=bc){
bcc[bc].push_back(st.top());
bccn[st.top()]=bc;
st.pop();
}
if(bcc[bc].size()>2){
for(auto x:bcc[bc]){
union_p(x,u);
}
}
}
}
else if(dfn[e[i]]<dfn[u]&&e[i]!=pre){
low[u]=min(low[u],dfn[e[i]]);
}
}
if(pre==0&&child==1) isc[u]=0;
}
int find_bcc(){
int ret=0,pr=1,bcnt=0;
df=bc=0;
for(int i=1;i<=n;++i) dfn[i]=low[i]=isc[i]=bccn[i]=0;
for(int i=1;i<=n;++i) if(!dfn[i]){
while(!st.empty()) st.pop();
tarjan(i,0);
int an = 0;
_for(j,pr,bc){
if(bcc[j].size()!=2) continue;
int ct=0;
for(auto x:bcc[j]){
if(isc[x]) ct++;
}
if(ct==1) an++;
}
ret+= (an+1)/2;
pr=bc+1;
bcnt++;
}
if(bcnt>1) ret++;
if(n==1) ret=0;
return ret;
}
int ine[maxn];
int head2[maxn],e2[maxn],net2[maxn],cnt2,vis[maxn],mk[maxn];
void add2(int u,int v){
e2[++cnt2]=v;
net2[cnt2]=head2[u];
head2[u]=cnt2;
}
int dfs(int u){
vis[u]=1;
int ret = (ine[u]<=1);
for(int i=head2[u];i;i=net2[i]){
if(!vis[e2[i]]){
ret+=dfs(e2[i]);
}
}
return ret;
}
int sol(){
_for(i,1,n) vis[i]=0;
int ct=0,ret=0,tmp,sum=0;
_for(i,1,n){
if(!vis[i]&&mk[i]){
ct++;
tmp=dfs(i);
if(tmp==1) ret += tmp;
else sum+=tmp;
}
}
ret += (sum+1)/2;
if(ct==1&&cnt2==0) ret=0;
return ret;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int ka=0;
while(cin>>n>>m){
ka++;
_for(i,1,n) p[i]=i;
int u,v;
_for(i,1,m){
cin>>u>>v;
add(u,v);
add(v,u);
}
find_bcc();
_for(i,1,n){
int px=find_p(i),py;
mk[px]=1;
for(int j=head[i];j;j=net[j]){
py=find_p(e[j]);
if(px!=py){
ine[py]++;
add2(px,py);
}
}
}
cout<<sol()<<"\n";
_for(i,1,n) head[i]=head2[i]=ine[i]=mk[i]=0;
cnt=cnt2=0;
}
return 0;
}