D153 拓扑排序 P2661 [NOIP 2015 提高组] 信息传递
D153 拓扑排序 P2661 [NOIP 2015 提高组] 信息传递_哔哩哔哩_bilibili
P2661 [NOIP 2015 提高组] 信息传递 - 洛谷
一个有向有环图,求最小环的大小。

思路
先用拓扑排序标记不在环里的点,不在环里的点一定能入队能出队,在环里的点一定不能入队,例如,图中 $5$ 和 $1$
可能存在多个简单环,再用深搜求出每个环的长度,最后取最小值
深搜时,要从环里的点进入,一直找到已访问的点
不可能从环里走出去,因为题目 “每人只会把信息告诉一个人” 限制了没有连向环外的边
注意:$vis$ 数组的双重功能,拓扑找环时,标记哪些点不在环内,DFS 时,标记哪些点已访问
注意:连边的方法,下标向值连边,赋值即完成连边,如 $t[1]=2,t[2]=4,t[4]=3...$
相关板子:
// 拓扑排序 O(n) #include<bits/stdc++.h> using namespace std; const int N=200010; int n,t[N],rd[N],ans=1e9; bool vis[N]; void topo(){ queue<int> q; for(int i=1;i<=n;i++) if(!rd[i]) q.push(i); while(!q.empty()){ int u=q.front();q.pop(); vis[u]=1; //u点能出队,说明其入度为0,标记u点不在环内 if(--rd[t[u]]==0) q.push(t[u]); } } void dfs(int x,int s){ //深搜求环长 if(vis[x]){ //发现x点已访问,那么更新答案 ans=min(ans,s); return; } vis[x]=1; //标记x点已访问 dfs(t[x],s+1); } signed main(){ cin>>n; for(int i=1;i<=n;i++){ cin>>t[i]; //下标i向值ti连边 rd[t[i]]++; //ti入度+1 } topo(); for(int i=1;i<=n;i++)if(!vis[i])dfs(i,0); //如果i在环内,则深搜 cout<<ans; }
// 有向最小环 并查集 #include<bits/stdc++.h> using namespace std; int n,cnt,ans=2e9; int fa[200005]; int find(int x){ ++cnt; return fa[x]==x?x:find(fa[x]); } int main(){ cin>>n; for(int i=1; i<=n; i++) fa[i]=i; for(int i=1,t; i<=n; i++){ cin>>t; cnt=0; find(t)==i?ans=min(ans,cnt):fa[i]=t; } cout<<ans; }
// 有向最小环 tarjan O(n) #include<bits/stdc++.h> using namespace std; const int N=200005; int n; int dfn[N],low[N],tim,stk[N],top,siz[N],scc[N],cnt; vector<int> e[N]; void tarjan(int x){ dfn[x]=low[x]=++tim; stk[++top]=x; for(int y:e[x]){ if(!dfn[y]){ tarjan(y); low[x]=min(low[x],low[y]); } else if(!scc[y]) low[x]=min(low[x],dfn[y]); } if(low[x]==dfn[x]){ ++cnt; while(1){ int y=stk[top--]; scc[y]=cnt; siz[cnt]++; if(y==x) break; } } } signed main(){ cin>>n; for(int i=1,x;i<=n;i++){ cin>>x; e[i].push_back(x); } for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i); int ans=2e9; for(int i=1;i<=n;i++) if(siz[i]>=2) ans=min(ans,siz[i]); cout<<ans; }
浙公网安备 33010602011771号