UVA10129 Play on Words
单词
一、题目描述
输入个单词,是否可以把所有这些单词排成一个序列,使得每个单词的第一个字母可上一个单词的最后一个字母相同(例如)。每个单词最多包含个小写字母。输入中可以有重复的单词。
二、知识铺垫
1、什么是欧拉图,什么是半欧拉图?
能一笔画的图就叫半欧拉图,如果起点和终点重合的话就是欧拉图。
2、有向图中半欧拉图的判定
一张 有向图 为 半欧拉图,当且仅当有向图的 基图连通,且存在一个且仅一个顶点的入度比出度大、一个且仅一个顶点的入度比出度小,其它所有顶点的入度等于出度。
注:基图为将原图的有向边改为无向边的图
说句人话就是:要想知道一个有向图是不是能一笔画,方法如下:
- ① 把它对应的无向图整出来 (基图)
- ② 判断基图是不是连通,方法:、 或者 并查集
- ③ 存在一个且仅一个顶点的入度比出度大、一个且仅一个顶点的入度比出度小,其它所有顶点的入度等于出度
三、题目解析
-
可以把到的看成到
-
因为只是看第一个字符和最后一个字符,所以可以把它们看成两个点,连上一条有向边,中间的字符不用管。
-
题目就会变成:在一个有向图中,找到一条路径,使得经过每条边一次并且仅一次。
是不是很熟悉?不是的你可以重学图论了
这就是 欧拉路径 的定义!
那么题目就会变成判断这个图是不是 半欧拉图
按 知识铺垫 中讲解的步骤处理即可。
四、判连通
#include <bits/stdc++.h>
using namespace std;
const int N = 30;
int g[N][N], in[N], out[N];
bool st[N]; //判断哪些字母输入了并用于连通性的判断
int n;
int start; //传入的节点
//连通性判断(需在要基图上判连通)
void dfs(int u) {
st[u] = 0; //遍历到的,就标识为0,这样处理后,如果还存在未标识为0的点,就表示它是孤立点
//用邻接矩阵,枚举26个可能
for (int i = 0; i < 26; i++)
// g[u][i]:u->i是不是有边
// st[i]:目标点是不是录入过,但没有标识过0
if (g[u][i] && st[i]) dfs(i); //深搜
}
bool solve() {
int num1 = 0, num2 = 0; //记录起点和终点的个数
dfs(start);
for (int i = 0; i <= 26; i++) {
//基图不连通的是不可能存在欧拉路径的
if (st[i]) return false; // i点出现过,但在dfs过程中却没有被标为0,那么i点不连通
//基图在判断完连通性后,退出历史舞台,下面开始的逻辑将全是有向图的逻辑
if (in[i] != out[i]) {
if (in[i] == out[i] + 1)
num1++;
else if (out[i] == in[i] + 1)
num2++;
else
return false; //出现其他情况就直接退出
}
}
//有向图是不是半欧拉图(一笔画图),需满足:
// ①:基图是不是连通
// ②:如果所有点的入度等于出度,那么此图是半欧拉图
// ③:如果存在某些点的入度与出度不一致,那么必须是一个入度比出度大1,另一个入度比出度小1,其它情况一票否决
return ((num1 == 1 && num2 == 1) || (num1 == 0 && num2 == 0));
}
int main() {
//加快读入
cin.tie(0), ios::sync_with_stdio(false);
int T;
cin >> T;
while (T--) {
cin >> n;
memset(st, 0, sizeof st);
memset(g, 0, sizeof g);
memset(in, 0, sizeof in);
memset(out, 0, sizeof out);
while (n--) {
string s;
cin >> s;
int l = s[0] - 'a';
int r = s[s.size() - 1] - 'a';
g[l][r]++, g[r][l]++; //因为半欧拉图就是一笔画图,是不是一笔画,需要先判断连通,而判连通需要基图,也就是对应的无向图,所以要创建无向图
in[l]++, out[r]++; //入度与出度,还是要按有向图来处理,说句人话就是把基图和有向图掺和在一起了
st[l] = st[r] = 1; //标识已出现过
start = l; //随意记录一个起点,以此起点开始进行dfs看看是不是图连通
}
if (solve())
printf("Ordering is possible.\n");
else
printf("The door cannot be opened.\n");
}
return 0;
}
五、并查集解法
#include <bits/stdc++.h>
using namespace std;
const int N = 30;
int p[N], in[N], out[N];
bool st[N];
int m;
int find(int x) {
return p[x] == x ? x : p[x] = find(p[x]);
}
void join(int a, int b) {
int fa = find(a), fb = find(b);
if (fa != fb) p[fa] = fb;
}
bool solve() {
//合并完后一共几个家族
int cnt = 0;
for (int i = 0; i < 26; i++)
if (p[i] == i && st[i]) cnt++;
if (cnt != 1) return false; //不是同一个家族说明图不连通
int num1 = 0, num2 = 0;
for (int i = 0; i <= 26; i++) {
if (in[i] != out[i]) {
if (in[i] == out[i] + 1)
num1++;
else if (out[i] == in[i] + 1)
num2++;
else
return false;
}
}
return (num1 == 1 && num2 == 1) || (num1 == 0 && num2 == 0);
}
int main() {
//加快读入
cin.tie(0), ios::sync_with_stdio(false);
int T;
cin >> T;
while (T--) {
cin >> m;
memset(st, 0, sizeof st);
memset(in, 0, sizeof in);
memset(out, 0, sizeof out);
for (int i = 0; i < 26; i++) p[i] = i; //并查集初始化
while (m--) {
string s;
cin >> s;
int l = s[0] - 'a';
int r = s[s.size() - 1] - 'a';
join(l, r);
in[l]++, out[r]++;
st[l] = st[r] = 1;
}
if (solve())
printf("Ordering is possible.\n");
else
printf("The door cannot be opened.\n");
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
2021-11-29 AcWing 算法提高课题解目录
2021-11-29 AcWing 1018. 最低通行费
2018-11-29 升级 Apache Tomcat的办法
2018-11-29 Windows 添加永久静态路由
2017-11-29 辽阳事情处理
2013-11-29 从Windows 服务器通过sync向Linux服务器定时同步文件