Files
python/TangDou/AcWing/EulerPath/P1341.md
HuangHai 1f397eca87 'commit'
2025-08-30 18:35:01 +08:00

143 lines
4.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## [$P1341$ 无序字母对](https://www.luogu.com.cn/problem/P1341)
### 一、题目描述
给定 $n$ 个各不相同的无序字母对(区分大小写,无序即字母对中的两个字母可以位置颠倒)。请构造一个有 ($n+1$) 个字母的字符串使得每个字母对都在这个字符串中出现。
**输入格式**
第一行输入一个正整数 $n$。
第二行到第 ($n+1$) 行每行两个字母,表示这两个字母需要相邻。
**输出格式**
输出满足要求的字符串。
如果没有满足要求的字符串,请输出 `No Solution`。
如果有多种方案,请输出字典序最小的方案(即满足前面的字母的 $ASCII$ 编码尽可能小)。
输入输出样例
输入 #1
```cpp {.line-numbers}
4
aZ
tZ
Xt
aX
```
输出
```cpp {.line-numbers}
XaZtX
```
### 二、解题思路
知识点:
如果图$G$中的一个路径包括每个边恰好一次,则该路径称为欧拉路径($Euler$ $path$)。
如果一个回路是欧拉路径,则称为欧拉回路($Euler$ $circuit$)。
简单点就是:从一个点出发,所有边都走了一次。
**解题**:
1.用并查集判断是否是欧拉回路。这时连通块只有一个。一图胜千言:
![](https://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/BlogImages/%7Byear%7D/%7Bmonth%7D/%7Bmd5%7D.%7BextName%7D/20230803140000.png)
2.对于无向图。如果图中的点全部都是偶点,则存在欧拉回路,任意点都可以。如果只有2个奇数点,则存在欧拉路,其中一个奇点是起点,另一个是终点。
![](https://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/BlogImages/%7Byear%7D/%7Bmonth%7D/%7Bmd5%7D.%7BextName%7D/20230803140024.png)
代码:
这里还要注意是$dfs$ 所以要 $n--$开始,而不是$0$开始
```cpp {.line-numbers}
#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;
}
```