You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

4.7 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

P1341 无序字母对

一、题目描述

给定 n 个各不相同的无序字母对(区分大小写,无序即字母对中的两个字母可以位置颠倒)。请构造一个有 (n+1) 个字母的字符串使得每个字母对都在这个字符串中出现。

输入格式 第一行输入一个正整数 n

第二行到第 (n+1) 行每行两个字母,表示这两个字母需要相邻。

输出格式 输出满足要求的字符串。

如果没有满足要求的字符串,请输出 No Solution

如果有多种方案,请输出字典序最小的方案(即满足前面的字母的 ASCII 编码尽可能小)。

输入输出样例 输入 #1

4
aZ
tZ
Xt
aX

输出

XaZtX

二、解题思路

知识点:

如果图G中的一个路径包括每个边恰好一次,则该路径称为欧拉路径(Euler path)。

如果一个回路是欧拉路径,则称为欧拉回路(Euler circuit)。

简单点就是:从一个点出发,所有边都走了一次。

解题

1.用并查集判断是否是欧拉回路。这时连通块只有一个。一图胜千言:

2.对于无向图。如果图中的点全部都是偶点则存在欧拉回路任意点都可以。如果只有2个奇数点则存在欧拉路其中一个奇点是起点另一个是终点。

代码:

这里还要注意是dfs 所以要 n--开始,而不是0开始

#include <bits/stdc++.h>
using namespace std;
const int N = 1010, M = N * N;
int g[N][N]; // 邻接矩阵
int start;   // 起点
int d[N];    // 点的度
char ans[M]; // 拼接欧拉路径
char s[3];   // 输入的字符串数组
int cnt;     // 计数器,可重复利用
int n;       // n个各不相同的无序字母对

// 并查集
int p[130]; // 'a'+26='z' 97+26=123
int find(int x) {
    if (x != p[x]) p[x] = find(p[x]);
    return p[x];
}

void dfs(int u) {
    for (int i = 64; i <= 125; i++)
        if (g[u][i]) {
            g[u][i]--, g[i][u]--;
            dfs(i);
        }
    ans[++cnt] = u;
}

int main() {
#ifndef ONLINE_JUDGE
    freopen("P1341.in", "r", stdin);
#endif
    scanf("%d", &n); // n个各不相同的无序字母对

    // 并查集初始化
    for (int i = 64; i <= 125; i++) p[i] = i;

    for (int i = 1; i <= n; i++) {
        scanf("%s", &s); // 读入n个无序字母对

        g[s[0]][s[1]]++, g[s[1]][s[0]]++; // 记录点与点之间的连通关系,无向图记录,此方法可记录重边,当然,本题不需要处理重边
        d[s[0]]++, d[s[1]]++;             // 以字母的ASCII码作为节点编号,记录度

        // 合并并查集,并查集的目的是看它们最终是不是连通的,如果点都不在一个集合中,就不可能出现欧拉路径
        int fx = find(s[0]), fy = find(s[1]);
        p[fx] = fy;
    }

    for (int i = 64; i <= 125; i++) // 枚举'A'~'Z','a'~'z'
        // d[i]表示此字母在上面的输入中出现过
        // p[i]=i 表示检查完的家族数量
        if (p[i] == i && d[i]) cnt++;

    if (cnt != 1) {
        puts("No Solution"); // 判断是否为欧拉
        return 0;
    }

    cnt = 0; // 记录度是奇数的节点个数
    for (int i = 64; i <= 125; i++)
        if (d[i] % 2 == 1) { // 度为奇数则必须是2个;当然,也可以全是偶数,没有奇数
            cnt++;
            if (start == 0) start = i; // 记录号最小的奇数度节点号,也就是出发点,因为本题要求输出字典序最小
        }

    // ① 奇数度节点个数是0是可以的此时比如是一个环
    // ② 奇数度节点个数是2是可以的此时一个是起点另一个是终点
    if (cnt && cnt != 2) { // 如果两个都不是,那肯定就不存在欧拉路径
        puts("No Solution");
        return 0;
    }

    // 又见经典套路,如果每个点的度都是偶数,那么,出发点可以是任意一个度大于零的点,那就找出最小的那个吧
    if (start == 0) {
        start = 64;
        while (!d[start]) start++;
    }
    // 通过dfs找出欧拉路径
    cnt = 0;
    dfs(start);

    // 输出最终的欧拉路径字符串
    for (int i = cnt; i; i--) printf("%c", ans[i]);

    return 0;
}