求最小的環的長度。 沒思路大概這已經卡死一小半人。
我的起初的想複雜了。 後來覺得:這道題真簡單:n個點,n-1條邊,如果沒有環的話,這是一棵樹。
這些題目應該不可能考察這些非常規的高難度的算法把。
求強聯分量
起初的想法,,然後在處理。
這些題目應該不可能考察這些非常規的高難度的算法把。
題外話:網上看了一個好東西:有人提出了這樣的算法。顯然該無向圖存在連通分支。對每個連通分支單獨進行考慮。
對於一個連通圖,任取一個它的生成樹(有算法可以完成這項操作),連通分支中除過這些樹枝剩下的邊我們稱作弦,每一條弦對應該連通圖的一個基本回路,無向圖的所有迴路都可以表示成這些基本回路的直和(邊集的並,去掉公共邊),所以最小回路一定在這些基本回路里產生。我們現在把這些弦一條一條往裏加就可以了。加入一條弦,立即產生一個基本回路,計算此迴路邊數並記錄在一個數組裏。去掉此弦,加入另一條弦,會有另一個迴路產生,計算邊數,保存。把所有的弦都試一遍,得到一個數組,取最小元素對應的弦,把弦加進生成樹,就得到最小回路。這樣的算法很節省空間。可以得到所有最小回路。
貌似合理:其實不行,看反例:
- fence1(1) / \ fence2(1),fence3(1) --- fence4(3) / \ fence5(1),fence6(1) ------fence7(6) \____/fence 8(1),fence9(1),fence10(1)可以發現,存在一種最小生成樹爲:
- fence1(1) / fence2(1) / \ fence5(1),fence6(1) \____/ fence8(1),fence9(1),fence10(1)
根據好友“川菜“思路:強聯通分量。
#include <iostream>
//#define debug
#include <bits/stdc++.h>
using namespace std;
const int MM=200001;
int T[MM];
vector<int> ReT[MM];
bool used[MM];
vector<int> xu;
int huan[MM];
void dfs(int v){
used[v]=true;
int next= T[v];
if( next !=-1 && !used[next] ) {
used[next]=true;
dfs( next ) ;
}
xu.push_back(v);
}
void dfs2(int v,int k){
huan[k]++;
#ifdef debug
cout <<"v :"<<v <<" ||"<<"huan:"<<k << " "<< huan[k] <<" ";
#endif
used[v]=true;
for(int i=0;i<ReT[v].size();i++){
int next=ReT[v][i];
if( next !=-1 && !used[next] ) {
used[next]=true;
dfs2( next , k ) ;
}
}
}
int main()
{
//freopen("message1.in","r",stdin);
//freopen("mesaage.out","w",stdout);
int N; cin>>N;
memset(T,-1,sizeof(T));
for(int i=1;i<=N;i++){
cin>>T[i];
//ReT[ T[i] ] = i;//這裏錯了。 反向以後,可能有好幾個兒子。
ReT[ T[i] ].push_back(i);
}
#ifdef debug
for(int i=1;i<=N;i++){
cout << "i:" <<i <<"next:"<< T[i]<<";" ;
}
#endif // debug
memset(used, 0 ,sizeof(used));
for(int i=1;i<=N;i++){
if( !used[i] ) {
dfs(i);
}
}
memset(used,0,sizeof(used));
memset(huan,0,sizeof(huan));
#ifdef debug
for(int i=0;i<xu.size();i++){
cout << "xu"<< i << ":" << xu[i]<<" ";
cout <<endl;
}
#endif // debug
int k=0;
for(int i=xu.size()-1;i>=0;i--){
if( !used[ xu[i] ] ) {
dfs2( xu[i], ++k );
}
}
int ans=MM;
for(int i=1;i<=k;i++){
if( huan[i]!=1) ans = min( huan[i] , ans );
}
cout << ans;
return 0;
}
思路二 拓撲 + DFS
先拓撲,刪掉入度爲零的點,剩下的就是環了。 然後DFS 記下每次訪問到的節點的時間戳,如果再次訪問到該節點,將此時的時間戳,減去上次的時間戳。這應該是正解。 有環的話,只有一個環。 只有一個環嗎? 我想錯了!!
先掃描一遍結點,將入度爲0的結點入隊; 然後從隊列裏依次取出這些結點,刪除它的出邊,刪除該結點(做刪除標記),修改出邊結點的入度,如果其入度爲0,則入隊。 重複上述過程直至隊列爲空,拓撲過程結束。 剩下的結點一定構成環。 、在掃描一遍結點,遇到入度不爲0的結點,就以它爲 起點 dfs,查環的長度 感覺思路對的,luogu上值通過了10組數據。 寫後感: 剛開始 拓撲排序不熟練,通過了6組數據, 還有四組內存超過了。
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
const int MM=200001;
int T[MM];
int rudu[MM];
int S;
int ans=MM;
int N;
bool used[MM];
//要優化,否則很多數據通不過
/* 1、先掃描一遍結點,將入度爲0的結點入隊;
* 然後從隊列裏依次取出這些結點,刪除它的出邊,刪除該結點(做刪除標記),修改出邊結點的入度,如果其入度爲0,則入隊。
* 重複上述過程直至隊列爲空,拓撲過程結束。 剩下的結點一定構成環。
*
* 2、在掃描一遍結點,遇到入度不爲0的結點,就以它爲起點查環的長度,同時刪除該結點(做刪除標記)。
*
*/
void tp(){
queue<int> q;
for(int v=0; v<=N; v++){
if( rudu[v]==0 ) {
q.push(v);
}
}
while( !q.empty()){
int f=q.front() ; q.pop();
int next = T[f];
T[f]=-1;
if(next!=-1)
{ rudu[next]--;
if( rudu[next]==0 ) {q.push( next ); }
}
}
}
void dfs(int v,int step){
int next= T[v];
if( next == S) {
ans = min( ans, step ) ;
return;
}
if( next !=-1 ) {
dfs( T[v] , step+1) ;
used[next]=true;
}
}
int main()
{
//freopen("message1.in","r",stdin);
// freopen("mesaage.out","w",stdout);
cin>>N;
memset(rudu,0,sizeof(rudu));
memset(used, 0 ,sizeof(used));
for(int i=1;i<=N;i++){
cin>>T[i];
rudu[ T[i] ]++;
}
tp();
//這裏可以不用深度搜索,直接while語句往下搜索,遇到起點後,統計一下環的長度,也方便
memset(used, 0 ,sizeof(used));
for(int i=1;i<=N;i++){
if( rudu[i]==1 && !used[i] ) {
S = i;
used[i]=true;
dfs(i, 1);
}
}
cout << ans;
return 0;
}
附: 數據2:
輸入:
50 18 14 38 26 36 27 23 13 21 4 34 41 22 50 47 11 12 11 24 1 47 37 28 48 28 17 396 4 10 48 42 8 2 50 49 32 36 21 20 23 45 5 30 46 19 44 3 20 33
輸出:
15
樣例解釋: