相關的概念:
割邊:在連通圖中,刪除了連通圖的某條邊後,圖不再連通。這樣的邊被稱爲割邊,也叫做橋。割點:在連通圖中,刪除了連通圖的某個點以及與這個點相連的邊後,圖不再連通。這樣的點被稱爲割點。
對於有向圖上的2個點a,b,若存在一條從a到b的路徑,也存在一條從b到a的路徑,那麼稱a,b是強連通的。
對於有向圖上的一個子圖,若子圖內任意點對(a,b)都滿足強連通,則稱該子圖爲強連通子圖。
非強連通圖有向圖的極大強連通子圖,稱爲強連通分量。
將有向圖的所有的有向邊替換爲無向邊,所得到的圖稱爲原圖的基圖。如果一個有向圖的基圖是連通圖,則有向圖是弱連通圖。
tarjan基本模板
#include<bits/stdc++.h>
using namespace std;
#define N 30000
int dfn[N];//深搜的次序
int low[N];//能追溯到的最早的次序
int head[N];//鄰接表
int belong[N];//屬於哪個強連通分量
int f[N];//父節點
bool instack[N];
int k,cnt,num;//k是鄰接表中邊的數量,cnt強連通分量個數,num深搜的次序。
struct edge
{
int u,v,next;
} e[N*5];
void add(int u,int v)
{
e[k].v=v;
e[k].next=head[u];
head[u]=k++;
}
stack<int>s;
void dfs(int u,int id)//有向圖不需要參數id
{
dfn[u]=low[u]=++num;
instack[u]=true;
s.push(u);
for(int i=head[u]; i!=-1; i=e[i].next)
{
if(i==(1^id)) continue;//無向圖需要這個條件
int v=e[i].v;
if(!dfn[v])
{
f[v]=u;
dfs(v,i);
low[u]=min(low[u],low[v]);
}
else if(instack[v])
low[u]=min(low[u],dfn[v]);
}
if(low[u]==dfn[u])
{
cnt++;
int v;
do
{
v=s.top();
s.pop();
instack[v]=false;
belong[v]=cnt;
}
while(u!=v);
}
}
int main()
{
int n,m,x,y;
cin>>n>>m;
memset(head,-1,sizeof(head));
while(m--)
{
cin>>x>>y;
add(x,y);
add(y,x);
}
for(int i=1; i<=n; i++)
{
if(!dfn[i])
{
dfs(i);//有向圖
dfs(i,-1);//無向圖
}
}
}
相關題目poj1236
題意:給出點的數量,給出與之相連的單向邊。至少選幾個點,能從這些點到達所有點。至少加多少條邊,能從任何一個點到達所有點。
思路:求強連通分量,縮點,求出新圖的入度爲0的點個數n,出度爲0的點個數m。
問題1:ans=n; 問題2:ans=max(n,m);
#include<iostream>
#include<cstring>
#include<stack>
using namespace std;
#define N 155
int n,k,num,cnt;
int head[N],dfn[N],in[N],out[N],belong[N],low[N];
bool instack[N];
struct edge
{
int u,v,next;
} e[N*N];
void add(int u,int v)
{
e[k].u=u;
e[k].v=v;
e[k].next=head[u];
head[u]=k++;
}
stack<int>s;
void dfs(int u)
{
dfn[u]=low[u]=++num;
instack[u]=true;
s.push(u);
for(int i=head[u]; i!=-1; i=e[i].next)
{
int v=e[i].v;
if(!dfn[v])
{
dfs(v);
low[u]=min(low[u],low[v]);
}
else if(instack[v])
low[u]=min(low[u],dfn[v]);
}
if(low[u]==dfn[u])
{
cnt++;
int v;
do
{
v=s.top();
s.pop();
instack[v]=false;
belong[v]=cnt;
}
while(u!=v);
}
}
int main()
{
memset(head,-1,sizeof(head));
cin>>n;
for(int i=1; i<=n; i++)
{
int x;
while(cin>>x,x)
{
add(i,x);
}
}
for(int i=1; i<=n; i++)
{
if(!dfn[i])
dfs(i);
}
for(int i=0; i<k; i++)//求出縮點後的新圖中的入度和出度
{
int x=belong[e[i].u],y=belong[e[i].v];
if(x!=y)
in[y]++,out[x]++;
}
int x=0,y=0;
for(int i=1; i<=cnt; i++)
{
if(!in[i]) x++;
if(!out[i]) y++;
}
if(cnt==1)//特例,強連通分量只有一個,不需要加邊
{
cout<<1<<endl;
cout<<0<<endl;
}
else
{
cout<<x<<endl;
cout<<max(x,y)<<endl;
}
}
poj2186
題意:有n只牛,牛A認爲牛B很牛,牛B認爲牛C很牛。給你M個關係(誰認爲誰牛),如果牛A認爲牛B很牛,牛B認爲牛C很牛。那麼我們就認爲牛A認爲牛C很牛。求大家都認爲它很牛的牛有幾隻。
思路:求強連通分量,縮點,出度爲0的點只能有一個,答案就是該連通分量中的點的數量。
#include<cstring>
#include<iostream>
#include<stack>
using namespace std;
#define N 30000
int dfn[N],low[N],head[N],belong[N],out[N];
bool instack[N];
int k,cnt,num;
struct edge
{
int u,v,next;
} e[N*5];
void add(int u,int v)
{
e[k].u=u;
e[k].v=v;
e[k].next=head[u];
head[u]=k++;
}
stack<int>s;
void dfs(int u)
{
dfn[u]=low[u]=++num;
instack[u]=true;
s.push(u);
for(int i=head[u]; i!=-1; i=e[i].next)
{
//if(i==(1^id)) continue;
int v=e[i].v;
if(!dfn[v])
{
dfs(v);
low[u]=min(low[u],low[v]);
}
else if(instack[v])
low[u]=min(low[u],dfn[v]);
}
if(low[u]==dfn[u])
{
cnt++;
int v;
do
{
v=s.top();
s.pop();
instack[v]=false;
belong[v]=cnt;
}
while(u!=v);
}
}
int main()
{
int n,m,x,y;
cin>>n>>m;
memset(head,-1,sizeof(head));
while(m--)
{
cin>>x>>y;
add(x,y);
}
for(int i=1; i<=n; i++)
{
if(!dfn[i])
{
dfs(i);
}
}
int c=0;
for(int i=0; i<k; i++)
{
if(belong[e[i].u]!=belong[e[i].v])
out[belong[e[i].u]]++;
}
for(int i=1; i<=cnt; i++)
if(!out[i]) c++;
if(c!=1)
cout<<0<<endl;
else
{
int ans=0;
for(int i=1; i<=n; i++)
if(out[belong[i]]==0)
ans++;
cout<<ans<<endl;
}
}
poj2762
題意:給出一個有向圖,求出該圖是否能滿足給出兩個點,能從一個a到b或者從b到a。
思路:一個強連通分量中的點肯定滿足,所以先縮點,在該弱連通圖中判斷改圖是否是單鏈的(toposort),若有分叉則分叉上的兩個點是不能互達的。
#include<cstring>
#include<iostream>
#include<stack>
#include<queue>
using namespace std;
#define N 3000
int dfn[N],low[N],head[N],belong[N],in[N],out[N];
bool instack[N];
int k,cnt,num;
struct edge
{
int u,v,next;
} e[N*5];
void add(int u,int v)
{
e[k].u=u;
e[k].v=v;
e[k].next=head[u];
head[u]=k++;
}
stack<int>s;
void dfs(int u)
{
dfn[u]=low[u]=++num;
instack[u]=true;
s.push(u);
for(int i=head[u]; i!=-1; i=e[i].next)
{
//if(i==(1^id)) continue;
int v=e[i].v;
if(!dfn[v])
{
dfs(v);
low[u]=min(low[u],low[v]);
}
else if(instack[v])
low[u]=min(low[u],dfn[v]);
}
if(low[u]==dfn[u])
{
cnt++;
int v;
do
{
v=s.top();
s.pop();
instack[v]=false;
belong[v]=cnt;
}
while(u!=v);
}
}
vector<int>E[N*5];//新圖的鄰接表
queue<int>q;
bool toposort()
{
while(!q.empty()) q.pop();
for(int i=1; i<=cnt; i++)
if(!in[i]) q.push(i);
if(q.size()>1) return false;//入點有多個不滿足
while(!q.empty())
{
int u=q.front();
q.pop();
for(int i=0; i<E[u].size(); i++)
{
int v=E[u][i];
if(!--in[v])
q.push(v);
}
if(q.size()>1) return false;//有分叉不滿足
}
return true;
}
int main()
{
int T;
cin>>T;
while(T--)
{
int n,m,x,y;
cin>>n>>m;
memset(head,-1,sizeof(head));
memset(dfn,0,sizeof(dfn));
memset(low,0,sizeof(low));
num=cnt=k=0;
while(m--)
{
cin>>x>>y;
add(x,y);
}
for(int i=1; i<=n; i++)
{
if(!dfn[i])
{
dfs(i);
}
}
memset(in,0,sizeof(in));
for(int i=1; i<=cnt; i++)
E[i].clear();
for(int i=0; i<k; i++)
{
int x=belong[e[i].u];
int y=belong[e[i].v];
if(x!=y)
{
in[y]++;
E[x].push_back(y);
}
}
if(toposort())
cout<<"Yes"<<endl;
else
cout<<"No"<<endl;
}
}
hdu4738
題意: 曹操有N個島,這些島用M座橋連接起來,每座橋有士兵把守(也可能沒有),周瑜想讓這N個島不連通,但只能炸掉一座橋,並且炸掉一座橋需要派出不小於守橋士兵數的人。
思路:首先判斷圖是否連通,不連通則不需要去炸橋,輸出0,圖連通,則可以用Tarjan找割邊,割邊不存在輸出-1表示不能達到目的,找到所有的割邊,只需要炸掉其中守兵數最少的橋即可。
PS: 橋的守兵數爲0時,也需要派出一個人去炸橋!
#include<bits/stdc++.h>
using namespace std;
#define N 2000
#define inf 0x7ffffff
int dfn[N],low[N],head[N],belong[N],f[N];
bool instack[N];
int k,cnt,num;
struct edge
{
int u,v,w,next;
} e[N*N];
void add(int u,int v,int w)
{
e[k].u=u;
e[k].v=v;
e[k].w=w;
e[k].next=head[u];
head[u]=k++;
}
stack<int>s;
void dfs(int u,int id)
{
dfn[u]=low[u]=++num;
instack[u]=true;
s.push(u);
for(int i=head[u]; i!=-1; i=e[i].next)
{
if(i==(1^id)) continue;
int v=e[i].v;
if(!dfn[v])
{
f[v]=u;
dfs(v,i);
low[u]=min(low[u],low[v]);
}
else if(instack[v])
low[u]=min(low[u],dfn[v]);
}
if(low[u]==dfn[u])
{
cnt++;
int v;
do
{
v=s.top();
s.pop();
instack[v]=false;
belong[v]=cnt;
}
while(u!=v);
}
}
int main()
{
int n,m,x,y,w;
while(cin>>n>>m,n+m)
{
memset(head,-1,sizeof(head));
memset(instack,false,sizeof(instack));
memset(f,-1,sizeof(f));
memset(dfn,0,sizeof(dfn));
memset(low,0,sizeof(low));
k=cnt=num=0;
while(m--)
{
cin>>x>>y>>w;
add(x,y,w);
add(y,x,w);
}
int c=0;
for(int i=1; i<=n; i++)
{
if(!dfn[i])
{
c++;
dfs(i,-1);
}
}
int ans=inf;
for(int i=0; i<k; i+=2)
{
int u=e[i].u;
int v=e[i].v;
int w=e[i].w;
if(belong[u]!=belong[v])//滿足此條件的是割邊
ans=min(ans,w);
}
if(c>1) ans=0;//不連通
else if(ans==0) ans=1;//沒有人把守也要派一個人去才能炸掉橋
else if(ans>=inf) ans=-1;//不存在割邊
cout<<ans<<endl;
}
}