1. 程式人生 > >poj 2762 Going from u to v or from v to u?(tarjan縮點+拓撲排序)

poj 2762 Going from u to v or from v to u?(tarjan縮點+拓撲排序)

題目連結:http://poj.org/problem?id=2762

題目意思:一個山洞有n個房間,m條單向邊,要任意兩個房間(假如房間x,y),要x可以到y,或者y到x(或者不是並且)

如果可以就輸出Yes,不是就輸出No。

 

思路:題目是或者不是並且,並且的話,直接求是否是強聯通圖即可。

或者的話,我們可以這樣想本質上是求該圖是否為資料結構中單向連通圖

因為可能有環所以要

1.先用tarjan縮點變成一個DAG(有向無環圖)。

2.再利用拓撲排序的運用,只要每次找入度為0的點時,點的數量不超過一個。

為什麼?我們可以這樣想,縮完點的圖可以看作一棵樹,子樹(即入度為0)不可以有兩個,因為他們不可以相互到達。

 

我想的時候有個錯誤的想法:(縮完點)不是不能有兩個子樹嗎?那我求每個點的出度,出度為1的點數是點數減一不就是答案嗎?還要縮什麼點?

它是個圖不是樹,說成樹是好理解。(縮完點的圖)反例:出度為1的點只有1個,但這是一個單向連通圖。

程式碼:

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
#define inf 0x3f3f3f3f
using
namespace std; typedef long long ll; const int maxn=1e3+100; struct node{ int next,to; }edge[maxn*maxn]; int head[maxn],low[maxn],dfn[maxn],belong[maxn],visit[maxn]; int in[maxn],st[maxn],n,m,cnt,tot,top,num; vector<int> ve[maxn];//記錄縮點之後的連線關係 queue<int> q; void init()//初始化 {
while(!q.empty()) q.pop(); memset(head,-1,sizeof(head)); memset(visit,0,sizeof(visit));//標記是否在棧內 memset(dfn,0,sizeof(dfn)); memset(low,0,sizeof(low)); memset(belong,0,sizeof(belong));//記錄每個點所屬於的縮點編號 memset(in,0,sizeof(in));//記錄每個縮點的入度 memset(st,0,sizeof(st));//st模擬棧 cnt=tot=top=num=0; } void add(int u,int v)//前向星連邊 { edge[cnt].to=v; edge[cnt].next=head[u]; head[u]=cnt++; } void tarjan(int u)//tarjan縮點模板 { dfn[u]=low[u]=++tot; visit[u]=1; st[++top]=u; for(int i=head[u];i!=-1;i=edge[i].next) { int v=edge[i].to; if(!dfn[v]) { tarjan(v); low[u]=min(low[u],low[v]); } else if(visit[v]) low[u]=min(low[u],dfn[v]); } if(low[u]==dfn[u]) { int t; num++;//計算縮點的數量 do{ t=st[top--]; visit[t]=0; belong[t]=num;////記錄這個點所屬於的縮點編號 }while(t!=u); } } bool solve() { for(int i=0;i<=maxn;i++) ve[i].clear(); for(int i=1;i<=n;i++)//tarjan縮點 if(!dfn[i]) tarjan(i); for(int u=1;u<=n;u++)//尋找每個縮點的關係 { for(int i=head[u];i!=-1;i=edge[i].next) { int v=edge[i].to; if(belong[u]!=belong[v])//不在一個縮點,那麼兩個點相連 { in[belong[v]]++;//入度 ve[belong[u]].push_back(belong[v]);//記錄邊的關係 } } } //拓撲排序 for(int i=1;i<=num;i++) if(!in[i])//入度為0的入佇列 q.push(i); if(q.size()>1)//入度為0的個數 return false; while(!q.empty()) { int u=q.front(); q.pop(); for(int i=0;i<ve[u].size();i++) { int v=ve[u][i]; in[v]--; if(!in[v]) q.push(v); } if(q.size()>1)//入度為0的個數 不能同時大於1 return false; } return true; } int main() { int t; scanf("%d",&t); while(t--) { init(); scanf("%d%d",&n,&m); int u,v; for(int i=1;i<=m;i++) { scanf("%d%d",&u,&v); add(u,v); } if(solve()) printf("Yes\n"); else printf("No\n"); } return 0; }