蓝桥杯训练——[蓝桥杯][2017年第八届真题]发现环

题目链接:https://www.dotcpp.com/oj/problem1841.html

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

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值