題目來源:
題目大意:
現在給你一個 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;
}