當我在准備做基環樹的題時,經常有了正解的思路確發現不會找環,,,,,,因為我實在太蒻了。
所以我准備梳理一下找環的方法:
有向圖
先維護一個棧,把遍歷到的節點一個個地入棧。當我們從一個節點x回溯時無非兩種情況:
1.從x延伸出去的環已經被找完;
2.從x延伸出去的地方並沒有環;
也就是說從x延伸出去的地方包括x都已經對我們現在毫無意義了。所以說,當一個點回溯時,把它出棧。
當下一步要到的點在棧中,那說明找到了環。此時把棧中的節點拎出來打上標記即可。
#include<iostream>
#include<cstring>
#include<cstdio>
#define maxn 500001
using namespace std;
struct edge{
int to,next;
edge(){}
edge(const int &_to,const int &_next){ to=_to,next=_next; }
}e[maxn];
int head[maxn],k;
int stack[maxn],top;
bool vis[maxn],instack[maxn],inloop[maxn];
int n,m;
inline int read(){
register int x(0),f(1); register char c(getchar());
while(c<'0'||'9'<c){ if(c=='-') f=-1; c=getchar(); }
while('0'<=c&&c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar();
return x*f;
}
inline void add(const int &u,const int &v){
e[k]=edge(v,head[u]);
head[u]=k++;
}
void dfs(int u){
vis[u]=instack[u]=true,stack[++top]=u;
for(register int i=head[u];~i;i=e[i].next){
int v=e[i].to;
if(!vis[v]) dfs(v);
else if(instack[v]){
int w,t=top;
do{
w=stack[t--],instack[w]=false,inloop[w]=true;
}while(w!=v);
}
}
instack[u]=false,top--;
}
int main(){
memset(head,-1,sizeof head);
n=read(),m=read();
for(register int i=1;i<=m;i++){
int u=read(),v=read();
add(u,v);
}
for(register int i=1;i<=n;i++) if(!vis[i]) dfs(i);
for(register int i=1;i<=n;i++) printf("%d:%d\n",i,inloop[i]);
return 0;
}
感性地理解一下還是能懂的?
無向圖
如果你用上面的方法來跑無向圖......你會發現紅得好凄慘
原因是:把無向邊也當成了環。
所以我們得換個方法。可以先跑個搜索樹出來,並記錄fa數組表示圖中節點在搜索樹上的父親節點,並記錄下搜索的時間戳dfn。當下個節點的dfn為0時,往下走;否則找到了環。
如果你真的像上面那樣做了,你會發現同一個環被記錄了兩次。如果你想優化常數,你當然不願意這種事發生;或者你想把環上節點輸出而不只是打個標記而已,你就更不能讓它發生了。這時我們記錄的dfn才是真正發揮作用的時候。我們可以判斷一下當前點和下個點的時間戳的大小關系。為了能夠輸出環上節點,我們才記錄了fa數組,這時它就發揮作用了:可以通過不斷遍歷fa數組把環輸出。但關鍵就是大小關系是什么?隨便寫一個嗎?肯定不是。可以知道dfn[fa[x]]<dfn[x],所以我們應該從環上dfn值最大的點開始往回走。所以說,用來判斷的大小關系為:if(當前節點的dfn值小於下一個點) then 從下一個點開始遍歷fa數組。
#include<iostream>
#include<cstring>
#include<cstdio>
#define maxn 500001
using namespace std;
struct edge{
int to,next;
edge(){}
edge(const int &_to,const int &_next){ to=_to,next=_next; }
}e[maxn<<1];
int head[maxn],k;
int dfn[maxn],fa[maxn],tot;
int n,m;
inline int read(){
register int x(0),f(1); register char c(getchar());
while(c<'0'||'9'<c){ if(c=='-') f=-1; c=getchar(); }
while('0'<=c&&c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar();
return x*f;
}
inline void add(const int &u,const int &v){
e[k]=edge(v,head[u]);
head[u]=k++;
}
void dfs(int u){
dfn[u]=++tot;
for(register int i=head[u];~i;i=e[i].next){
int v=e[i].to;
if(v==fa[u]) continue;
if(!dfn[v]) fa[v]=u,dfs(v);
else if(dfn[u]<dfn[v]){
printf("%d",v);
do{
printf(" %d",fa[v]),v=fa[v];
}while(v!=u);
puts("");
}
}
}
int main(){
memset(head,-1,sizeof head);
n=read(),m=read();
for(register int i=1;i<=m;i++){
int u=read(),v=read();
add(u,v),add(v,u);
}
for(register int i=1;i<=n;i++) if(!dfn[i]) dfs(i);
return 0;
}
不過無向圖的算法就找不出一元環了。但哪題用到了一元環呢?
兩個算法的復雜度都是O(N)。