求最小的环的长度。 没思路大概这已经卡死一小半人。
我的起初的想复杂了。 后来觉得:这道题真简单: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
样例解释: