0
点赞
收藏
分享

微信扫一扫

UVA10129 Play on Words

​​单词 \(Play\) \(on\) \(Words\)​​

一、题目描述

输入\(n(n≤100000)\)个单词,是否可以把所有这些单词排成一个序列,使得每个单词的第一个字母可上一个单词的最后一个字母相同(例如\(acm,malform,mouseacm,malform,mouse\))。每个单词最多包含\(1000\)个小写字母。输入中可以有重复的单词。

二、知识铺垫

1、什么是欧拉图,什么是半欧拉图?

能一笔画的图就叫半欧拉图,如果起点和终点重合的话就是欧拉图。

2、有向图中半欧拉图的判定

一张 有向图半欧拉图,当且仅当有向图的 基图连通,且存在一个且仅一个顶点\(u\)的入度比出度大\(1\)、一个且仅一个顶点\(v\)的入度比出度小\(1\),其它所有顶点的入度等于出度。

注:基图为将原图的有向边改为无向边的图

说句人话就是:要想知道一个有向图是不是能一笔画,方法如下:


    • ① 把它对应的无向图整出来 (基图)
    • ② 判断基图是不是连通,方法:\(dfs\)、\(bfs\)
  • ③ 存在一个且仅一个顶点的入度比出度大\(1\)、一个且仅一个顶点的入度比出度小\(1\),其它所有顶点的入度等于出度

三、题目解析

  • 可以把\(\mathrm{a}\)到\(\mathrm{z}\)的看成\(0\)到\(25\)
  • 因为只是看第一个字符和最后一个字符,所以可以把它们看成两个点,连上一条有向边,中间的字符不用管。
  • 题目就会变成:在一个有向图中,找到一条路径,使得经过每条边一次并且仅一次。

是不是很熟悉?不是的你可以重学图论了
这就是 欧拉路径 的定义!

那么题目就会变成判断这个图是不是 半欧拉图

知识铺垫 中讲解的步骤处理即可。

四、\(dfs\)判连通

#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;
}



举报

相关推荐

0 条评论