历届试题 发现环
时间限制:1.0s 内存限制:256.0MB
问题描述
小明的实验室有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
反思错误:
①想到了并查集,但是没有细想,忽略了成环的条件是当前两点已经存在于同一个集合之中了。
②想了想最费劲的方法,循环将每个出入度为一的点及其连接的线删除,直到剩下所有出入度为二的点。(特别耗时,循环太多)
③最操蛋的是,思路没问题了,C能过,Java超时。(至今无法解决,什么时候解决,什么时候再来修改吧)【PS:现在优化好了,在原来的基础上加上了快读、快输,又优化了dfs 、 点点关系结构 和 并查集存储结构】
解题思路:
并查集就不细说了,直接看这题,
在每次循环输入中,我们对两个点find_root(查看是否在同一个集合之中),不在同一个集合中则放到同一集合下,如果在同一个集合中,那么这两点一定在环上(因为同一集合代表两点间有一条路了,这次循环输入又出来一条路了)。
那么我们只需要用dfs,从一个点到另一个点,不重边经过的所有节点就是构成环的所有点。排序,输出。
Java代码:
import java.io.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.StringTokenizer;
public class Main {
public static void main(String[] args) {
//此处实例化快读
IN cin = new IN(System.in);
So so = new So();
so.sol(cin);
}
//解决方案 具体实现部分
static class So{
int[] f;//并查集,关系存储
void sol(IN cin){
int N = cin.nextInt();
f = new int[N+1];
List<Integer>[] tree = new List[N+1];//点点间的关系,因为不知道是几叉树的树状结构,所以用链表好用
//实例化
for(int i=1;i<=N;++i) {
// ArraysList还是数组结构,LinkedList是链表。时隔多年又发现了以前错误的理解
tree[i]=new ArrayList<>();
}
int rings = 0,ringe = 0;//rings :环开始的值,ringe:环结束的值
for(int i=1;i<=N;++i) {
int x = cin.nextInt(), y = cin.nextInt();
if(rings==0) {
if(union(x,y)) {
rings = x;
ringe = y;
}
tree[x].add(y);
tree[y].add(x);
}
}
int[] res = new int[N+1];
boolean[] vis = new boolean[N+1];
int top = dfs(tree,rings,ringe,res,0,vis);
Arrays.sort(res,0,top);
for(int i=0;i<top;++i) {
cout.print(res[i]+" ");
}
cout.flush();
}
//并查集搜索根(压缩深度)
int find(int x) {
return f[x] == 0 ? x : (f[x] = find(f[x]));//(f[x]=find(f[x]))在每次查询中,递归将底层都指向根
}
//两点合并并查集,若在一个并查集中返回true
boolean union(int x,int y) {
int a = find(x), b = find(y);
if(a==b) return true;
f[a] = b;
return false;
}
//DFS,访问过的点用vis标识,避免重复扫描
int dfs(List<Integer>[] tree,int cur,int ringe,int[] res,int top,boolean[] vis) {
if(top>0&&cur==ringe) {res[top]=cur;return top+1;}
res[top] = cur;
vis[cur]=true;
for(int next:tree[cur]) {
if(vis[next])continue;
int h=dfs(tree,next,ringe,res,top+1,vis);
if(h!=0)return h;
}
return 0;
}
}
// 快写
static PrintWriter cout = new PrintWriter(System.out);
//下边这个类是 快读
static class IN{
private BufferedReader reader;
private StringTokenizer tokenizer;
public IN(InputStream is){
reader = new BufferedReader(new InputStreamReader(is),32768);
tokenizer = null;
}
public String next() {
while(tokenizer==null || !tokenizer.hasMoreTokens()) {
try {
tokenizer = new StringTokenizer(reader.readLine());
}catch(IOException e) {
throw new RuntimeException(e);
}
}
return tokenizer.nextToken();
}
public int nextInt() {
return Integer.parseInt(next());
}
}
}
C++代码(一百分,思路和Java一样):
#include <stdio.h>
#include <string.h>
#include <vector>
#include <algorithm>
using namespace std;
const int maxn = 100000+5;
int par[maxn], vis[maxn], ret[maxn];
vector<int> edge[maxn];
int n, s, f;
int findRoot(int x) {
return par[x] == x ? x : par[x] = findRoot(par[x]);
}
void dfs(int u, int ind) {
ret[ind] = u;
if(u == f) {
sort(ret, ret + ind + 1);
for(int i = 0; i <= ind; i++) {
printf("%d%c", ret[i], i==ind?'\n':' ');
}
return;
}
vis[u] = 1;
for(int i = 0; i < edge[u].size(); i++) {
int v = edge[u][i];
if(!vis[v]) dfs(v, ind+1);
}
vis[u] = 0;
}
int main() {
while(scanf("%d", &n) == 1) {
int u, v;
for(int i = 1; i <= n; i++) par[i] = i;
for(int i = 0; i < n; i++) {
scanf("%d%d", &u, &v);
int ru = findRoot(u), rv = findRoot(v);
if(ru == rv) s = u, f = v;
else {
par[ru] = rv;
edge[u].push_back(v);
edge[v].push_back(u);
}
}
memset(vis, 0, sizeof(vis));
dfs(s, 0);
}
return 0;
}