zl程序教程

您现在的位置是:首页 >  其他

当前栏目

UVA10129 Play on Words

On words Play
2023-09-27 14:28:12 时间

单词 \(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;
}