《信息学奥赛一本通 (C++)版》骑马修栅栏(fence)

131 阅读4分钟

题目出处:《信息学奥赛一本通 (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。 具体可参加下图:

image.png

题解代码:

#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;
}