第六次CCF計算機軟體能力認證考試(第四題)
阿新 • • 發佈:2019-01-09
問題描述
某國有n個城市,為了使得城市間的交通更便利,該國國王打算在城市之間修一些高速公路,由於經費限制,國王打算第一階段先在部分城市之間修一些單向的高速公路。
現在,大臣們幫國王擬了一個修高速公路的計劃。看了計劃後,國王發現,有些城市之間可以通過高速公路直接(不經過其他城市)或間接(經過一個或多個其他城市)到達,而有的卻不能。如果城市A可以通過高速公路到達城市B,而且城市B也可以通過高速公路到達城市A,則這兩個城市被稱為便利城市對。
國王想知道,在大臣們給他的計劃中,有多少個便利城市對。 輸入格式 輸入的第一行包含兩個整數n, m,分別表示城市和單向高速公路的數量。
接下來m行,每行兩個整數a, b,表示城市a有一條單向的高速公路連向城市b。 輸出格式 輸出一行,包含一個整數,表示便利城市對的數量。 樣例輸入 5 5
1 2
2 3
3 4
4 2
3 5 樣例輸出 3 樣例說明
城市間的連線如圖所示。有3個便利城市對,它們分別是(2, 3), (2, 4), (3, 4),請注意(2, 3)和(3, 2)看成同一個便利城市對。 評測用例規模與約定 前30%的評測用例滿足1 ≤ n ≤ 100, 1 ≤ m ≤ 1000;
前60%的評測用例滿足1 ≤ n ≤ 1000, 1 ≤ m ≤ 10000;
所有評測用例滿足1 ≤ n ≤ 10000, 1 ≤ m ≤ 100000。
現在,大臣們幫國王擬了一個修高速公路的計劃。看了計劃後,國王發現,有些城市之間可以通過高速公路直接(不經過其他城市)或間接(經過一個或多個其他城市)到達,而有的卻不能。如果城市A可以通過高速公路到達城市B,而且城市B也可以通過高速公路到達城市A,則這兩個城市被稱為便利城市對。
國王想知道,在大臣們給他的計劃中,有多少個便利城市對。 輸入格式 輸入的第一行包含兩個整數n, m,分別表示城市和單向高速公路的數量。
接下來m行,每行兩個整數a, b,表示城市a有一條單向的高速公路連向城市b。 輸出格式 輸出一行,包含一個整數,表示便利城市對的數量。 樣例輸入 5 5
1 2
2 3
3 4
4 2
3 5 樣例輸出 3 樣例說明
城市間的連線如圖所示。有3個便利城市對,它們分別是(2, 3), (2, 4), (3, 4),請注意(2, 3)和(3, 2)看成同一個便利城市對。 評測用例規模與約定 前30%的評測用例滿足1 ≤ n ≤ 100, 1 ≤ m ≤ 1000;
前60%的評測用例滿足1 ≤ n ≤ 1000, 1 ≤ m ≤ 10000;
所有評測用例滿足1 ≤ n ≤ 10000, 1 ≤ m ≤ 100000。
題目大意: 給定一個有向圖,求出哪些點之間可以相互達到?最後輸出的是,可以相互到達的點的對數
思路:這道題明顯是求 強連通分量的個數,假設某個連通分量裡面節點數為n,那麼該連通分量的對數為 n*(n-1)/2 , 只要把所有連通分量的對數加起來就是整張圖的對數
本題關鍵還是求 強連通分量的個數。我網上查了一下,決定用 Tarjan演算法。
Tarjan演算法:
核心還是dfs深度遍歷;
用陣列dfn[i]表示搜尋到該節點所需要的時間戳、low[i]表示節點i直接或者間接達到的時間最小的點;
當u入棧,掃描u能夠達到的所有節點,如果節點v沒有訪問過,那就先dfs遍歷v, low[u] = min(low[u],low[v]) ,如果v在棧裡,low[u] = min(low[u],dfn[v]); 當low[u] = dfn[u] 時,棧內節點u以及u以上所有的點全部出棧,這些點為一個強連通子圖的節點。
以上時演算法的實現步驟,關於證明,呃。。我也不太明白
#include <iostream>
#include <vector>
using namespace std;
#define MAX 10010
int n; //表示節點數目
vector<int> g[MAX]; //動態陣列,節約空間
int Bcnt; //強連通分量個數
int Top; //棧頂
int Index; //時間戳
int low[MAX],dfn[MAX]; //Tarjan演算法裡的兩個重要陣列
int belong[MAX],stack[MAX]; //belong陣列表示該節點屬於哪一個連通分量
bool instack[MAX];
int answer = 0;
void Init_tarjan(){
Bcnt = Top = Index = 0;
for(int i=1;i<=n;i++)
low[i] = dfn[i] = 0;
}
void Tarjan(int u){
stack[Top++] = u; //將元素u入棧
instack[u] = 1; //標記元素已經入棧
low[u] = dfn[u] = ++Index;
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(instack[v])
low[u] = min(low[u],dfn[v]);
}
if(low[u] == dfn[u]){
Bcnt++;
int sum = 0;
int v;
do{
v = stack[--Top]; //出棧
instack[v] = 0;
belong[v] = Bcnt;
sum++; //表示連通子圖中節點的個數
}while(u!=v);
sum = (sum * (sum-1))/2;
answer = answer + sum;
}
}
int main(){
int m; //圖中的邊數
cin>>n>>m;
while(m--){
int x,y;
cin>>x>>y;
g[x].push_back(y);
}
Init_tarjan(); //先初始化
for(int i=1;i<=n;i++){
if(!dfn[i])
Tarjan(i);
}
cout<<answer<<endl;
return 0;
}