题目出处:《信息学奥赛一本通 (C++)版》P468页 线上OJ:[ybt.ssoier.cn:8088/problem_sho…] 本题属于图论中的欧拉图。
背景知识:
如果一个图存在一笔画,则一笔画的路径叫做欧拉路,如果最后又回到起点,那这个路径叫做欧拉回路。
奇点的定义:如果与一个点相连的边的数目为奇数,则该点称为奇点。对于能够一笔画的图,我们有以下两个定理。 定理1:存在欧拉路的条件:图是连通的,有且只有2个奇点(一个开头,一个结尾)。 定理2:存在欧拉回路的条件:图是连通的,有0个奇点(因为开头和结尾也是连通的)。
注1:不能依赖于顶点的数量,因为数据有可能是跳点,比如1,5,6,7。只有4个点,但是编号从1到7。 注2:由于两个端点之间的栅栏只要修一次,所以 e[u][i] 和 e[i][u] 其实是同一个栅栏,只要访问一次即可。 注3:所谓“500进制表示法中最小的一个”,其实就是0~499之间每次输出最小的。
核心思想:
本题实际为无向的欧拉图。题中提到“所有栅栏都是连通的”,所以这是一个连通图。栅栏就是边,栅栏的两端就是顶点。但本题的输入只有边,没有顶点。所以需要记录顶点编号的最小值和最大值,作为dfs时的上下限。
本题可以采用dfs进行,但需先找到起始的奇点。如果没有奇点,则从最小编号 minn 开始。根据注3,每次dfs时从最小的点开始继续向下dfs。
基于上述原则,所以题中的输出样例为:1 2 3 4 2 5 4 6 5 7。 具体可参加下图:
题解代码:
#include<bits/stdc++.h>
#define N 510
using namespace std;
int e[N][N], du[N]; // e[i][j]为1,表示从i→j有栅栏; du[i]表示节点i的度。度为1的点是起始或终止节点
int maxn = -1; // 本题未说明编号数量,且最终给出的节点编号可能不连续。故用maxn记录最大的顶点编号,作为搜索上限。dfs时不会漏点
int minn = 510; // 用 minn 记录最小节点编号,以免测试数据不是从1开始,dfs无法继续
stack<int> stk;
void Hierholzer(int u)
{
for(int i = minn; i <= maxn; i++) // 本题要求按照字典序顺序,故从小到大搜索即可
{
if(e[u][i]) // 如果u和i存在边,则对i继续进行dfs,同时为了避免这条变被重复走,修改 e[u][i]和e[i][u]为0
{
e[u][i]--;
e[i][u]--;
Hierholzer(i);
}
}
stk.push(u);
}
/*
如果一个图存在一笔画,则一笔画的路径叫做欧拉路,如果最后又回到起点,那这个路径叫做欧拉回路。
奇点的定义:如果与一个点相连的边的数目为奇数,则该点称为奇点。对于能够一笔画的图,我们有以下两个定理。
定理1:存在欧拉路的条件:图是连通的,有且只有2个奇点(一个开头,一个结尾)。
定理2:存在欧拉回路的条件:图是连通的,有0个奇点(因为开头和结尾也是连通的)。
*/
int main()
{
int F;
cin >> F; // 读入边的数量
int x, y;
for(int i = 1; i <= F; i++)
{
cin >> x >> y; // 读入顶点坐标
maxn = max(maxn, max(x, y)); // 更新最大顶点编号
minn = min(minn, min(x, y)); // 更新最小顶点编号
e[x][y]++; // e[x][y]为边
e[y][x]++; // e[y][x]亦为边
du[x]++; // 顶点x的度+1
du[y]++; // 顶点y的度+1
}
// 根据定理1和2,寻找奇点作为DFS起始点。如果没有奇点,说明为欧拉回路,从任意一个点均可出发。
int start = minn; //如果有奇点,就从奇点开始寻找。如果没有奇点,由于本题中需要按照最小字典序输出,故从minn开始dfs
for (int i = minn; i <= maxn; i++) // 从小到大搜索
if (du[i]%2 == 1) // 第一个度为1的节点,作为dfs起始点
{
start = i;
break;
}
Hierholzer(start); // 从start开始进行dfs
while (!stk.empty())
{
printf("%d\n", stk.top()); // 由于是逆序存储,所以正序输出即可
stk.pop();
}
return 0;
}