題意:
N個點的有向圖G,若有路從u通向v,則稱u可達v(v不一定可達u)。現給出各點之間的連接關係。
Q1:求出至少從幾個點出發,才能使得所有的點均可以遍歷一遍;
Q2:至少需要添加幾條單向邊,才能使得從這些點中任何一個出發都可以遍歷全部點(使G強連通)。
分析:
和最近學的離散數學有關。這個題目可以用tarjan求圖G強連通分量(縮點)。如果只有一個強連通分量則圖G強連通,任意兩點可達,如果有多個強連通分量,則考察這些分量(縮點後成爲新圖結點),考慮這些點中入度爲0的點:
i )入度爲0的點不能被其他點到達,而一個入度不爲0的點可以從某個入度爲0的點到達,從這些入度爲0的點出發即可遍歷全部結點;。
ii)出度爲0的點,在圖G中,入度爲0的點(設爲m個)無法從其他點到達,那麼爲了使得所有的點連通,需要m條路徑連接到這m個入度爲0的點;而出度爲0的點(設爲n個)無法到達其他點,那麼爲了使得所有的點連通,需要n條路徑從這n個出度爲0的點連出。於是,至少需要添加 max(m, n)條邊,使得圖中所有的點的入度和出度不爲0.
同時,在一個DAG中,如果該圖的所有點均可連接到一塊,且每個點的出度和入度均不爲0,則該圖肯定強連通。於是,結果爲 max(m,n)。
代碼如下:
#include <bits/stdc++.h>
const int N = 105;
int sccno[N],dfn[N],low[N],scc_cnt,dfs_clock;
vector<int> G[N];
stack<int> s;
//自定義
int n;
int sum[N],point[N];
int _in[N],_out[N];
//
void dfs(int u){
dfn[u] = low[u] = ++dfs_clock;
s.push(u);
for(int i=0;i<G[u].size();i++){
int v = G[u][i];
if(!dfn[v]){
dfs(v);
low[u] = min(low[u],low[v]);
}else if(!sccno[v]){
low[u] = min(low[u],dfn[v]);
}
}
if(dfn[u] == low[u]){
scc_cnt++;
int tot = s.size();
for(;;){
int x = s.top();
s.pop();
sccno[x] = scc_cnt;
if(x==u)break;
}
sum[scc_cnt] = tot - s.size();//縮點
}
}
void tarjan(int n){
memset(dfn,0,sizeof(dfn));
for(int i=1;i<=n;i++){
if(!dfn[i])dfs(i);
}
}
void solve(){
scc_cnt = dfs_clock = 0;
for(int i=0;i<=n;i++)G[i].clear();
for(int i=1;i<=n;i++){
int x;
while(cin>>x&&x){
G[i].push_back(x);
}
}
tarjan(n);
memset(_in,0,sizeof(_in));
memset(_out,0,sizeof(_out));
for(int u=1;u<=n;u++){
for(int j=0;j<G[u].size();j++){
int v = G[u][j];
if(sccno[u] != sccno[v]){
_in[sccno[v]] ++;//縮點後求入度出度
_out[sccno[u]] ++;
}
}
}
int deg_in = 0,deg_out = 0;
for(int i=1;i<=scc_cnt;i++){
if(_in[i] == 0)deg_in ++;
if(_out[i] == 0)deg_out ++;
}
//輸出結果
if(scc_cnt == 1){//只有一個強連通分量,原圖強連通
printf("1\n0\n");
}
else{
printf("%d\n%d\n",deg_in,max(deg_in,deg_out));
}
}
int main(){
std::ios::sync_with_stdio(false);
std::cin.tie(0);
while(cin>>n){
solve();
}
return 0;
}