題目描述:
小明的實驗室有N臺電腦,編號1~N。原本這N臺電腦之間有N-1條數據鏈接相連,恰好構成一個樹形網絡。在樹形網絡上,任意兩臺電腦之間有唯一的路徑相連。
不過在最近一次維護網絡時,管理員誤操作使得某兩臺電腦之間增加了一條數據鏈接,於是網絡中出現了環路。環路上的電腦由於兩兩之間不再是隻有一條路徑,使得這些電腦上的數據傳輸出現了BUG。
爲了恢復正常傳輸。小明需要找到所有在環路上的電腦,你能幫助他嗎?
輸入描述:
第一行包含一個整數N。
以下N行每行兩個整數a和b,表示a和b之間有一條數據鏈接相連。
對於30%的數據,1 <= N <= 1000
對於100%的數據, 1 <= N <= 100000, 1 <= a, b <= N
輸入保證合法。
輸出描述:
按從小到大的順序輸出在環路上的電腦的編號,中間由一個空格分隔。
輸入樣例:
5
1 2
3 1
2 4
2 5
5 3
輸出樣例:
1 2 3 5
此題兩種解法:(1)先用並查集找出環路中的相鄰兩點,再用搜索(bfs)找出兩點間的路徑。
(2)拓撲排序,只將度爲1 的點入隊,最終沒有進隊列的點即爲環路節點
並查集+bfs:
#include <iostream>
#include <cmath>
#include <algorithm>
#include <cstring>
#include <vector>
#include <map>
#include <queue>
#include <cstdio>
#include <string>
#include <stack>
#include <set>
#define IOS ios::sync_with_stdio(false), cin.tie(0)
using namespace std;
typedef long long ll;
//鏈式前向星邊信息
struct node
{
int y,next_s;/* data */
}a[200010];
//並查集邊信息
struct node1
{
int x,y;/* data */
}c[100010];
int head[100010];//head[x]以x爲起點的某條邊的編號
int cnt;//邊號計數
int n;//
int f[100010];//並查集祖先數組
bool vis[100010];//訪問數組
int pre[100010];//pre[x]=y指向直接父節點(x的直接父節點是y)
void init(){
memset(head,-1,sizeof(head));
cnt=0;
}
void add(int x,int y){
a[cnt].y=y;
a[cnt].next_s=head[x];
head[x]=cnt++;
}
int find(int x){
int r=x;
while(r!=f[r])r=f[r];
int j=x;
while(f[j]!=r){
f[j]=r;
j=f[x];
x=j;
}
return r;
}
void bfs(int st,int en,int &ans){
queue<int > p;
int book=0;
//先判斷是否st和en之間存在兩條邊,如果存在,不需要bfs搜索
for(int i=head[st];i!=-1;i=a[i].next_s){
int v=a[i].y;
if(v==en){
book++;
}
else {
vis[v]=true;
pre[v]=st;
p.push(v);
}
}
if(book==2){
ans=2;
memset(vis,false,sizeof(false));
vis[st]=vis[en]=true;
return ;
}
vis[st]=true;
//排除了st,en自成環的可能
while(!p.empty()&&!vis[en]){
int t=p.front();
p.pop();
for(int i=head[t];i!=-1&&!vis[en];i=a[i].next_s){
int v=a[i].y;
if(vis[v])continue;
pre[v]=t;//用於回溯標記
vis[v]=true;
p.push(v);
}
}
while(!p.empty())p.pop();
memset(vis,false,sizeof(vis));
vis[st]=true;
ans=1;
//利用pre數組從en回溯查找到st,並且標記
for(int i=en;i!=st;i=pre[i]){
vis[i]=true;
ans++;
}
}
int main()
{
IOS;
init();
int x,y;
cin>>n;
for(int i=1;i<=n;i++){
f[i]=i;
}
for(int i=0;i<n;i++){
cin>>x>>y;
c[i].x=x;
c[i].y=y;
add(x,y);//無向圖建立兩條有向邊
add(y,x);
}
int st,en;
for(int i=0;i<n;i++){
int tx=find(c[i].x);
int ty=find(c[i].y);
if(tx==ty){//找到迴路中的兩個點
st=c[i].x;
en=c[i].y;
break;
}
//養成習慣:讓小根作爲根節點
if(tx>ty){
f[tx]=ty;
}
else f[ty]=tx;
}
int ans=0;//統計環上節點個數
bfs(st,en,ans);
//按順序輸出
for(int i=1;i<=n;i++){
if(vis[i]){
cout<<i;
if(ans>1)cout<<" ";
else cout<<endl;
ans--;
}
}
getchar();
getchar();
return 0;
}
拓撲排序:
#include <iostream>
#include <cmath>
#include <algorithm>
#include <cstring>
#include <vector>
#include <map>
#include <queue>
#include <cstdio>
#include <string>
#include <stack>
#include <set>
#define IOS ios::sync_with_stdio(false), cin.tie(0)
using namespace std;
typedef long long ll;
struct node_1
{
int y,next_s;/* data */
}a[200010];
int head[100010];
int cnt=0;
int du[100010];
bool del[100010];//del[i]=true,i節點被刪除
void init(){
memset(head,-1,sizeof(head));
cnt=0;
}
void add(int x,int y){
a[cnt].y=y;
a[cnt].next_s=head[x];
head[x]=cnt++;
}
int main()
{
IOS;
int n,x,y;
init();
cin>>n;
for(int i=1;i<=n;i++){
cin>>x>>y;
add(x,y);
add(y,x);
du[x]++;
du[y]++;
}
queue<int > p;
int del_cnt=0;
//刪除度爲1的節點
for(int i=1;i<=n;i++){
if(du[i]==1){
del_cnt++;
del[i]=true;
p.push(i);
}
}
while(!p.empty()){
int t=p.front();
p.pop();
for(int i=head[t];i!=-1;i=a[i].next_s){
int v=a[i].y;
if(del[v])continue;//已經刪除
du[v]--;//相鄰接點度減一
if(du[v]<=1){//判斷是否要刪除
del[v]=true;
del_cnt++;
p.push(v);
}
}
}
for(int i=1;i<=n;i++){
if(del[i])continue;
if(++del_cnt==n)cout<<i<<endl;
else cout<<i<<" ";
}
getchar();
getchar();
return 0;
}