P1341 无序字母对[欧拉路]
题目描述
给定n个各不相同的无序字母对(区分大小写,无序即字母对中的两个字母可以位置颠倒)。请构造一个有n+1个字母的字符串使得每个字母对都在这个字符串中出现。
解析
毒瘤字符串读入
我就是不喜欢邻接矩阵,你咬我啊。
首先看到题目,由于存在n个不相同的无序字母对,且答案中要求题给字母对两字母相邻,容易分析出每个在字母对中出现的字母在答案中都至少要出现一次。即,如果数据有解,必然有出现字母种类\(<=\)n。
接下来看题目给出的另一条件,其要求我们构造一个含\(n+1\)个字母的含有所有给定字母对的字符串,并且每个字母对的两个字母需要相邻。那么就意味着在答案中一个字母对不能重复出现,否则字符串长度至少为\(n+2\)。
“相邻”是一种显而易见的抽象关系,我们在处理抽象关系时,往往要想到利用图论的特性限制这些抽象关系。
也就是说,我们把每个字母看成点,如果两个字母相邻,我们就在其间连一条无向边。那么连完之后,我们会发现我们要构造的答案其实就是一条路径,答案就是路径上的点。并且这条路径要在经过所有点的情况下不经过重复的边,因为经过了重复的边等同于一个字母对出现了两次,这是题目不允许的。
分析到这里,是不是很熟悉?
是的,这显然是要求一个欧拉路,那么我们直接求就行了。
这里给出欧拉路存在的条件:
- 无向图是连通图
- 图中恰好有两个节点度数为奇数,其余节点度数都为偶数。
感性证明:1就不用证了,只要稍微想想就能明白。。。对于2,其实奇数点就是欧拉路的起点和终点。显然,如果一个点度数为偶数,那么它可以被一条路径进入至多度数/2次,一条路径也可以从这个点出去至多度数/2次。如果一个点度为奇数,那么一条路径在进进出出它偶数次后,最后一次要不然是从这个点出去要不然是从某个点到这个点。
所以我们只要通过一些特判对不构成欧拉路的图输出无解就得了。
顺便也讲一下为什么要回溯时用栈来储存路径,而不是dfs时边往下搜边记录。
dfs时,系统栈如果遇到死路,不代表这个图就没有欧拉路了,因为dfs进程还在继续进行。意思就是说我们要记的路径和系统栈回溯时经过的路径是很可能不同的。
为什么呢?我们要输出的显然是在搜索树上一条符合要求的搜索路径,就是要搜到底,搜到把图中所有点都遍历到之后在再回溯时的路径,我们记下来才是对的。而系统栈回溯的时候,可能是因为走到了错误的分支,这个时候我们要是也记下来,毫无疑问是错的。
所以,我们等到系统栈因为搜索树最后一层都搜完而执行回溯时再记录路径。
参考代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<string>
#include<cstdlib>
#include<queue>
#include<vector>
#define INF 0x3f3f3f3f
#define PI acos(-1.0)
#define N 2010
#define MOD 2520
#define E 1e-12
using namespace std;
inline int read()
{
int f=1,x=0;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
return x*f;
}
vector<int> g[N];
int m,n,top,minn=INF,deg[N];
char stack[N];
bool v[N][N];
inline void euler(int x)
{
for(int i=0;i<g[x].size();++i){
int y=g[x][i];
if(v[x][y]||v[y][x]) continue;
v[x][y]=v[y][x]=1;
euler(y);
}
stack[++top]=x;
}
int main()
{
m=read();
for(int i=1;i<=m;++i){
char s[3];
scanf("%s",s);
int x=s[0],y=s[1];
g[x].push_back(y),g[y].push_back(x);
deg[x]++,deg[y]++;
minn=min(minn,min((int)x,(int)y));
n=max(n,max((int)x,(int)y));
}
for(int i=minn;i<=n;++i)
sort(g[i].begin(),g[i].end());
int k=0,st=0;
for(int i=minn;i<=n;++i)
if(deg[i]&1){++k;if(!st)st=i;}//起点必须ASCII最小,且为奇点
if(!st)
for(int i=minn;i<=n;++i)
if(deg[i]){st=i;break;}//只有偶点
if(k!=2&&k){printf("No Solution");return 0;}//欧拉图存在性判断
euler(st);
if(top!=m+1){printf("No Solution");return 0;}//连通性判断
for(int i=top;i;--i) printf("%c",stack[i]);
return 0;
}