题目描述
给定n个各不相同的无序字母对(区分大小写,无序即字母对中的两个字母可以位置颠倒)。请构造一个有n+1个字母的字符串使得每个字母对都在这个字符串中出现。输入输出格式
输入格式:
第一行输入一个正整数n。以下n行每行两个字母,表示这两个字母需要相邻。
输出格式:
输出满足要求的字符串。如果没有满足要求的字符串,请输出“No Solution”。
如果有多种方案,请输出前面的字母的ASCII编码尽可能小的(字典序最小)的方案
输入输出样例
输入样例#1:
4
aZ
tZ
Xt
aX
输出样例#1:
XaZtX
P1341 无序字母对
此题看描述可知是一道欧拉回路相关题. (后面发现其实是欧拉路径)
通过所给的信息,找出一条欧拉回路,通过每一个顶点(字符)回到原点. 取字典序最小的输出.
只不过是把结点由数字表示变成了字母,用字母存即可,似乎不难. 有下面三个需要思考的问题
1.如何判断是否存在欧拉回路?
2.怎么找欧拉回路?
3.如何保证字典序最小?
第一个问题: OK, 吸取之前的教训,这回不能忘掉图不一定联通.那么,如果图不连通,肯定不存在欧拉回路.而如果图联通,当且仅当所有节点的度数都为偶数时,欧拉回路才成立
第二个问题:确认存在欧拉回路后, 找欧拉回路就是一个图的遍历过程.遍历这张图的每个点得到的路径,就一定是欧拉回路
第三个问题: 这就是遍历的技巧了,从小到大遍历,找到第一条路径就跳出,就是此题的答案了.
思考好了,下面准备Code
区分大小写, 只有字母, 那么我们只需存52个点. 数据较小.
大写为0-25, 小写为26-51;
我用邻接矩阵来存图.
咦, 突然发现这样存图是不对的. 序号不是连续的. 那么可以吗?
似乎也是可以, 只要记录哪些点存在,不存在的点直接忽略即可.
发现存图是可以优化的, 开个84大小的数组, 获取字符直接减’A’, 使得代码编辑较为简单 似乎很水没啥用
出现了顺序变反的问题. 开始不知道哪里错, 明明遍历的顺序是从小到大的,怎么会出错.
仔细检查之后,发现是push和递归的顺序反了.应该先push在递归dfs,不然就是里层的先push.它是一个堆栈的结构
然后提交了, 提交了, 可是只得了10分.呜呜呜.分数这么低看来就是样例过了,肯定是思路全错了.呜呜呜.
还是先测试测试数据再说
6
aZ
aT
ZT
ET
bE
Tb
测了这样一组数据, 发现我的答案错了,别人AC的代码是我心目中的对的, 那么我的代码有问题.
而且发现,欧拉回路好像就是把所有的边都经过一次,而点是可以经过多次的,好吧,点的问题是哈密顿回路.
Ok, 现在新增了一个Euler函数,用来找欧拉回路的.原来的dfs只是用来判断联通性.其实也可以合起来的.
… 还是10分. 哪里出了问题. 崩溃了.
额, 看了别人的题解之后猛然发现, 其实是求欧拉路径而不是回路, 只要有路径就OK.
呵呵, 现在是20分了,有了一倍的进步!
先贴20分代码, 然后从头再来.
#include <iostream>
#include <vector>
#include <cstring>
using namespace std;
const int maxn = 84;
int G[maxn][maxn] = {}, degree[maxn] = {};
bool vis[maxn] = {}, exist[maxn] = {};
vector<int> node;
void dfs(int u) // 判断图是否联通
{
for (int i = 0; i < maxn; ++i) {
if (G[u][i] && !vis[i] && exist[i]) {
vis[i] = true;
dfs(i);
}
}
}
void Euler(int u)
{
for (int i = 0; i < maxn; ++i) {
if (G[u][i]) {
G[u][i] = G[i][u] = 0;
node.push_back(i);
Euler(i);
}
}
}
int main()
{
int n, cnt = 0;
char ca, cb;
cin >> n;
for (int i = 0; i < n; ++i) {
int a, b;
cin >> ca >> cb;
a = ca - 'A';
b = cb - 'A';
G[a][b] = G[b][a] = 1;
degree[a]++, degree[b]++;
exist[a] = exist[b] = true;
}
int _min = maxn;
for (int i = 0; i < n; ++i) {
if (degree[i] & 1) { // 存在奇度点.
cnt++;
_min = min(i, _min);
}
}
if (cnt > 2 || cnt == 1) {
cout << "No Solution";
return 0;
}
int param = -1;
if (cnt == 2) param = _min;
else {
for (int i = 0; i < maxn; ++i) {
if (exist[i]) {
param = i; // 找到图中字典序最小的那个.
break;
}
}
}
vis[param] = true;
dfs(param);
for (int i = 0; i < maxn; ++i) {
if (exist[i] && !vis[i]) { // 图不连通
cout << "No Solution";
return 0;
}
}
node.push_back(param);
Euler(param);
for (int i = 0; i < node.size(); ++i) {
cout << char(node[i] + 'A');
}
cout << endl;
}
/*
4
aZ
tZ
Xt
aX
6
aZ
aT
ZT
ET
bE
Tb
2
ab
bc
*/
嗯, 按照一份题解的思路(几乎跟我的一毛一样啊, 而且它还没有判断联通性!) , 我重写了一份代码, 结果得了50分, 对了一半.
题解的思路似乎和我是一样的, 但是我仔细对比了一下差异, 发现它的存储欧拉路径的方式(时间, 位置)和我略有不同. 我是每个结点访问时存进去, 而他是在每个Euler函数结束部分push进去. 事实证明他是对的我是错了.
这是为什么呢? 很抓狂, 不知道为什么是对的, 它就是对的; 不知道哪里错了, 它确实错了.
总之记住了一点, 找欧拉路径上的结点, 要在函数最后压进去, 然后倒叙输出
#include <iostream>
#include <vector>
using namespace std;
const int maxn = 84;
int G[maxn][maxn] = {};
int degree[maxn] = {};
string error = "No Solution";
vector<int> node;
void Euler(int u)
{
for (int i = 0; i < maxn; ++i) {
if (G[u][i]) {
G[u][i] = G[i][u] = 0;
Euler(i);
}
}
node.push_back(u); // 欧拉路径上的结点.
}
int main()
{
int n, cnt = 0;
cin >> n;
for (int i = 0; i < n; ++i) {
char a, b;
cin >> a >> b;
int c, d;
c = a - 'A', d = b - 'A';
G[c][d] = G[d][c] = 1;
degree[c]++, degree[d]++;
}
int _min = maxn;
for (int i = 0; i < maxn; ++i) {
if (degree[i] & 1) {
cnt++;
_min = min(_min, i);
}
}
if (cnt != 2 && cnt != 0) {
cout << error;
return 0;
}
if (cnt == 0) {
for (int i = 0; i < maxn; ++i) {
if (degree[i]) {
_min = i;
break;
}
}
}
Euler(_min);
for (int i = node.size() - 1; i >= 0; --i) {
cout << char(node[i] + 'A');
}
}