欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

POJ 2513、Trie树、并查集以及欧拉路径

程序员文章站 2022-06-03 18:33:51
...

POJ 2513、Trie树、并查集以及欧拉路径

这道题做的时候,干的最sb的事就是断点打印的东西没删干净,结果导致一开始怎么交怎么WA,后来怎么交怎么AC。不过仍然有很困惑的地方,就是在CSDN blog中,他的Trie树第二维只有15,但是能过,就很奇怪,而且将其改到应该是正确的26,却Runtime Error。

主要是三个方面,Trie树、并查集和欧拉回路/路径。并查集用于检查是否为连通图,不是必须的,但是可以减少2/3的运行时间,用DFS也可以做,但是不能使用std::vector前向星来储存图,太慢了,应该用链表式的前向星。

Trie树

Trie树的作用是判断输入的颜色是否已经出现过,一开始用hash做,以为hash散列开来就不会用太多时间,事实证明是不行的。Trie树还可以用来根据单词前缀寻找单词或者统计,稍作修改即可。一开始一直使用参考的Trie树,直接开一个二维数组保存,也不知道为什么他用15都能过,26可能是因为开的数组太大。

/* Trie-Tree using 2d array */
struct Trie
{
    int node_cnt;
    int tree[N][26]; // 0 in tree means no child
    int has_color[N];
    Trie()
    {
        node_cnt = 0;
        std::memset(tree, 0, sizeof(tree));
        std::memset(has_color, 0, sizeof(has_color));
    }
    int index(char ch)
    {
        return ch - 'a';
    }
    void insert(char ch[])
    {
        int root = 0, len = strlen(ch), idx;
        for (int i = 0; i < len; ++i)
        {
            idx = index(ch[i]);
            if (!tree[root][idx])
                tree[root][idx] = ++node_cnt;
            root = tree[root][idx];
        }
        has_color[root] = ++color_cnt;
    }
    int find(char ch[])
    {
        int root = 0, len = strlen(ch), idx;
        for (int i = 0; i < len; ++i)
        {
            idx = index(ch[i]);
            if (!tree[root][idx])
                return 0; // not found
            root = tree[root][idx];
        }
        return has_color[root];
    }
    int find_or_insert(char ch[])
    {
        int root = 0, len = strlen(ch), idx;
        for (int i = 0; i < len; ++i)
        {
            idx = index(ch[i]);
            if (!tree[root][idx])
                tree[root][idx] = ++node_cnt;
            root = tree[root][idx];
        }
        if (!has_color[root])
            has_color[root] = ++color_cnt;
        return has_color[root];
    }
};

这样的存储方式,其实也是按需分配,但可能实际并没有这么多的节点,所以超内存了,参考另一篇博客,使用树状存储则没有问题,代码见下。

/* Trie-Tree using linked tree */
struct TrieNode
{
    bool is_color;
    int color_id;
    TrieNode *t[26];
    TrieNode()
    {
        is_color = false;
        memset(t, NULL, sizeof(t));
    }
};
struct Trie
{
    int node_cnt;
    int has_color[N];
    TrieNode *root;
    Trie()
    {
        root = new TrieNode();
        node_cnt = 0;
    }
    int index(char ch)
    {
        return ch - 'a';
    }
    int find_or_insert(char ch[])
    {
        TrieNode *cur = root;
        int len = strlen(ch), idx;
        for (int i = 0; i < len; ++i)
        {
            idx = index(ch[i]);
            if (!cur->t[idx])
                cur->t[idx] = new TrieNode();
            cur = cur->t[idx];
        }
        if (!cur->is_color)
        {
            cur->is_color = true;
            cur->color_id = ++color_cnt;
        }
        return cur->color_id;
    }
};

并查集

并查集(Union & Find)用于判断图的连通性,其通过建立树状结构来组织不同的集合,当我们想要判断两个节点是否在同一集合中,只要查找这两个节点的根节点是否为同一个就行了。我们先定义find操作,通过递归查找找到当前节点的根节点。

int parent[N];
void init()
{
    for (int i = 0; i < N; ++i)
        parent[i] = i; // 一开始自成一派
}
int find(int u)
{
    return parent[u] == u ? u : find(parent[u]);
}

比如我们现在有五个节点0 1 2 3 4,假设有union操作如下,经过union(0, 1); union(1, 2); union(3, 4)之后的树状结构应该是下图,对应数组1 2 2 4 4。

void union(int u, int v)
{
    parent[u] = v;
}

POJ 2513、Trie树、并查集以及欧拉路径

此时进行union(1, 3),则数组会变成1 4 2 4 4,如下图

POJ 2513、Trie树、并查集以及欧拉路径

这样的连通性显然是不对的,因此union操作应该将两树的根节点接在一起,才能让这两个集合连通,对应数组1 2 4 4 4

void union(int u, int v)
{
    int root1 = find(u);
    int root2 = find(v);
    if (root1 != root2)
        parent[root1] = root2;
}

POJ 2513、Trie树、并查集以及欧拉路径

当然如果每次都沿着整个树查找,将会是非常低效的,因此我们在沿树查找的过程中,将一颗子树中所有的节点的父节点都指向根节点

int find(int u)
{
    return parent[u] == u ? u : parent[u] = find(parent[u]); // 这种骚操作我当然是想不到的
}

上文的union(1, 3)不能体现这个效果,所以我们换成union(0, 3),执行find(0)后如下图,对应数组2 2 2 4 4

POJ 2513、Trie树、并查集以及欧拉路径

执行find(3)之后树不变,整个函数执行完后如下图,对应数组2 2 4 4 4

POJ 2513、Trie树、并查集以及欧拉路径

这个时候如果执行一遍find(0),则重排会变成4 3 4 4 4,对应下图

POJ 2513、Trie树、并查集以及欧拉路径

欧拉回路/路径

解决了连通性的问题,那么要判断欧拉回路/路径,则非常简单,只要统计一下度数为奇数的节点个数就行了,如果为0或者2,则构成欧拉回路或路径。

完整代码

#include <cstdio>
#include <cstring>
const int N = 500005;
int color_cnt = 0;
struct TrieNode
{
    bool is_color;
    int color_id;
    TrieNode *t[26];
    TrieNode()
    {
        is_color = false;
        memset(t, NULL, sizeof(t));
    }
};
struct Trie
{
    int node_cnt;
    int has_color[N];
    TrieNode *root;
    Trie()
    {
        root = new TrieNode();
        node_cnt = 0;
    }
    int index(char ch)
    {
        return ch - 'a';
    }
    int find_or_insert(char ch[])
    {
        TrieNode *cur = root;
        int len = strlen(ch), idx;
        for (int i = 0; i < len; ++i)
        {
            idx = index(ch[i]);
            if (!cur->t[idx])
                cur->t[idx] = new TrieNode();
            cur = cur->t[idx];
        }
        if (!cur->is_color)
        {
            cur->is_color = true;
            cur->color_id = ++color_cnt;
        }
        return cur->color_id;
    }
};
int degree[N] = {0};
int origin[N];
int find_origin(int u)
{
    return origin[u] == u ? u : origin[u] = find_origin(origin[u]);
}
void connect(int u, int v)
{
    int o1 = find_origin(u);
    int o2 = find_origin(v);
    if (o1 != o2)
        origin[o1] = o2;
}
int main()
{
    char aa[20], bb[20];
    Trie *t = new Trie();
    for (int i = 0; i < N; ++i)
        origin[i] = i;
    while (scanf("%s %s", aa, bb) != EOF)
    {
        int pa = t->find_or_insert(aa);
        int pb = t->find_or_insert(bb);
        connect(pa, pb);
        ++degree[pa];
        ++degree[pb];
    }
    bool is_connected = true;
    int source = -1;
    for (int i = 1; is_connected && i <= color_cnt; ++i)
    {
        if (source == -1)
            source = find_origin(i);
        else if (find_origin(i) != find_origin(source))
            is_connected = false;
    }
    int degree_cnt = 0;
    for (int i = 1; i <= color_cnt; ++i)
    {
        if (degree[i] & 1)
            ++degree_cnt;
    }
    if (degree_cnt <= 2 && is_connected)
        printf("Possible\n");
    else
        printf("Impossible\n");
}