51nod 2463 旅行

12 篇文章 0 订阅
2 篇文章 0 订阅

题目链接

题意:

给定 m m m条无向边,可以随机一个点出发,按照无向边走完所有的城市,到了每个城市之后,可以有两种选择:

  1. 选择一条与当前城市相连的道路,走向一个没有去过的城市
  2. 沿着第一次访问该城市时经过的道路后退到上一个城市

当回到起点时,可以选择结束这次旅行或继续旅行。在每到达一个新的城市(包括起点)时,将城市的编号记录下来,这样会形成一个长度为 n n n的序列,请输出字典序最小的序列。

题解:

题目当中的数据只给了 m = n − 1 m=n-1 m=n1或者 m = n m=n m=n这两种情况。由于起点可以自由选择,因此我们起点肯定要选择1。我们可以发现,第一种情况其实就是表明这个图没有环。那么对于这种情况,我们只需要对每个城市连接的边的终点城市编号从小到大排序,然后直接 d f s dfs dfs顺序走完每个节点,在遍历的时候将编号存入 a n s ans ans数组当中,最后输出即可。
重点是第二种情况。这种情况表明在之前的无环图中加了一条边,由于之前的图其实是一棵树,加了一条边之后,图中一定会形成一个环。例如:
第一种情况的图是:
在这里插入图片描述
那么第二种情况图会变成:
在这里插入图片描述
对于这种图中只有一个环的图,我们称之为基环图。

我们可以发现,在遍历图的时候,环上总有一条边不会被经过。因此,我们可以枚举环上哪条边不会被经过,然后在 d f s dfs dfs的时候,如果经过这条边直接忽略即可。然后再按照第一种情况的 d f s dfs dfs策略即可找出答案。

实现细节见代码:

#include <bits/stdc++.h>
using namespace std;

int flag, u1[10010], v1[10010], ans[5010], res[5010], all, cnt, du, dv;
bool vis[5010], cir[5010];
vector<int> e[5010];
void dfs1(int from, int pre) { // 找到唯一的环,并存储环上的所有边,方便之后的枚举
	vis[from] = 1;
	for (int i = 0; i < e[from].size(); i++) {
		int to = e[from][i];
		if (to == pre) continue;
		if (vis[to]) {
			flag = 1;
			cir[to] = true;
			cir[from] = true;
			u1[++cnt] = from;
			v1[cnt] = to;
			return;
		}
		dfs1(to, from);
		if (flag && cir[to]) {
			if (cir[from]) {
				flag = 0;
				u1[++cnt] = from;
				v1[cnt] = to;
				return;
			}
			else {
				cir[from] = true;
				u1[++cnt] = from;
				v1[cnt] = to;
				return;
			}
		}
	}
}
void dfs2(int from, int pre) {
	if (vis[from]) return;
	vis[from] = true;
	res[++all] = from;
	for (int i = 0; i < e[from].size(); i++) {
		int v = e[from][i];
		if (v == pre) continue;
		if ((from == du && v == dv) || (from == dv && v == du)) { // 如果是不被走的边,直接continue
			continue;
		}
		dfs2(v, from);
	}
}
void dfs3(int from, int pre) {
	if (vis[from]) return;
	vis[from] = true;
	ans[++all] = from;
	for (int i = 0; i < e[from].size(); i++) {
		int v = e[from][i];
		if (v == pre) continue;
		dfs3(v, from);
	}
}
int check() { // 保证字典序最小
	for (int i = 1; i <= all; i++) {
		if (res[i] < ans[i]) {
			return 1;
		}
		else if (res[i] > ans[i]) {
			return 0;
		}
	}
	return 0;
}
int main() {
	int n, m;
	scanf("%d %d", &n, &m);
	int p = m;
	while (p--) {
		int u, v;
		scanf("%d %d", &u, &v);
		e[u].push_back(v);
		e[v].push_back(u);
	}
	for (int i = 1; i <= n; i++) {
		sort(e[i].begin(), e[i].end()); // 排序保证字典序最小
	}
	if (n == m) {
		dfs1(1, 0);
		int flag = 1;
		for (int i = 1; i <= cnt; i++) {
			du = u1[i], dv = v1[i];
			memset(vis, false, sizeof vis);
			all = 0;
			dfs2(1, 0);
			if (all < n) continue;
			if (flag) {
				for (int i = 1; i <= all; i++) {
					ans[i] = res[i];
				}
				flag = 0;
			}
			else {
				if (check()) {
					for (int i = 1; i <= all; i++) {
						ans[i] = res[i];
					}
				}
			}
		}
	}
	else {
		dfs3(1, 0);
	}
	for (int i = 1; i <= n; i++) {
		if (i - 1) printf(" ");
		printf("%d", ans[i]);
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值