题目描述:
小明的实验室有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;
}