【题意】给定n个点的有向图(无重边),问能否删除一条边使得全图无环。n<=500,m<=10^5。
【算法】拓扑排序
【题解】找到一个简单环,则欲删除的边一定经过该环。尝试环上的每一条边(至多n条边)后再次拓扑排序判断全图是否有环。
拓扑排序后定位到简单环:剩余图是环+环内DAG,DFS过程中将走入死路的点标-1,访问过标1,找到访问过的点就是简单环。换起始点直到找到环为止。
复杂度O(nm)。
#include<cstdio> #include<queue> #include<algorithm> using namespace std; const int maxn=600,maxm=100010; struct edge{int v,from;}e[maxm]; int map[maxn][maxn],tot,cnt,n,m,first[maxn],p,vis[maxn],in[maxn],deg[maxn],suc[maxn]; queue<int>Q; void insert(int u,int v){tot++;e[tot].v=v;e[tot].from=first[u];first[u]=tot;in[v]++;} void dfs(int x,int fa){ if(p||vis[x]==-1)return; if(vis[x]==1){p=x;suc[fa]=x;return;} vis[x]=1; for(int i=first[x];i;i=e[i].from)if(deg[e[i].v]>0){ dfs(e[i].v,x); if(p){if(fa&&!suc[p])suc[fa]=x;break;} } if(!p)vis[x]=-1; } bool solve(int o){ cnt=0; for(int i=1;i<=n;i++){deg[i]=in[i];if(i==e[o].v)deg[i]--;if(deg[i]==0)Q.push(i),cnt++;} while(!Q.empty()){ int x=Q.front();Q.pop(); for(int i=first[x];i;i=e[i].from)if(i!=o&&--deg[e[i].v]==0)Q.push(e[i].v),cnt++; } if(cnt==n)return 1; return 0; } int main(){ scanf("%d%d",&n,&m); for(int i=1;i<=m;i++){ int u,v; scanf("%d%d",&u,&v); insert(u,v);map[u][v]=tot; } cnt=0; for(int i=1;i<=n;i++){deg[i]=in[i];if(in[i]==0)Q.push(i),cnt++;} while(!Q.empty()){ int x=Q.front();Q.pop(); for(int i=first[x];i;i=e[i].from)if(--deg[e[i].v]==0)Q.push(e[i].v),cnt++; } if(cnt==n){printf("YES");return 0;} for(int i=1;i<=n;i++)if(deg[i]>0&&!p)dfs(i,0); int pp=p; do{ if(solve(map[p][suc[p]])){printf("YES");return 0;} p=suc[p]; }while(p!=pp); printf("NO"); return 0; }
另一种解法:枚举点i,in[i]--,拓扑排序找环。这样相当于删除一条指向n的边后全图找环。