1 条题解

  • 0
    @ 2025-8-24 21:44:56

    自动搬运

    查看原文

    来自洛谷,原作者为

    avatar Heartlessly
    AFO

    搬运于2025-08-24 21:44:55,当前版本为作者最后更新于2019-05-06 07:12:08,作者可能在搬运后再次修改,您可在原文处查看最新版

    自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多

    以下是正文


    Description

    给定 n (1n3×104)n\ (1 \leq n \leq 3 \times 10^4) 个总长不超过 m (1m3×105)m\ (1 \leq m \leq 3 \times 10^5) 的互不相同的字符串,现在你可以任意指定字符之间的大小关系。问有多少个串可能成为字典序最小的串,并输出这些串。

    Solution

    看到与字典序有关的问题,很容易想到建一棵 Trie(字典树)

    对于每一个字符串,我们可以设它的字典序是所有字符串中最小的。

    也就是说,这个字符串的第 ii 个字母 在 Trie 的第 ii 层(根节点算第 00 层)的所有字母中 字典序最小。

    设这个字符串的第 ii 个字母为 uu,我们可以连单向边 uvu \to v,表示我们指定了 uu 的字典序比 vv 小,其中 vv 是第 ii 层的其它字母。若这个字符串是其它某个字符串的前缀,则这个字符串不可能成为字典序最小的串,比如说 abbaabba 的字典序一定比 abab 大。当 2626 个字母间的关系形成环时,也一定不能成为字典序最小的串。

    怎么判断是否形成环呢?可以用 tarjan\rm tarjan 或者 拓扑排序

    这里我采用了 拓扑排序 。我们从入度为 00 的点开始,不断删去与它相连的边,并修改其它点的入度,将新的入度为 00 的点加入队列。若队列已空,但还存在入度不为 00 的点,则说明图存在环,反之则有解。

    时间复杂度为 O(26m)O(26m)

    Code

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long LL;
    
    template <class T>
    inline void read(T &x) {
        x = 0;
        char c = getchar();
        bool f = 0;
        for (; !isdigit(c); c = getchar()) f ^= c == '-';
        for (; isdigit(c); c = getchar()) x = x * 10 + (c ^ 48);
        x = f ? -x : x;
    }
    
    template <class T>
    inline void write(T x) {
        if (x < 0) {
            putchar('-');
            x = -x;
        }
        T y = 1;
        int len = 1;
        for (; y <= x / 10; y *= 10) ++len;
        for (; len; --len, x %= y, y /= 10) putchar(x / y + 48);
    }
    
    const int MAXN = 3e4, MAXM = 3e5;
    int n, ans;
    string s[MAXN + 5];
    bool ok[MAXN + 5];
    struct Trie {
        int tot, in[26], e[26][26], ch[MAXM + 5][26];
        bool ed[MAXM + 5];
        queue<int> q;
        
        inline void insert(string x) {
            int u = 1, len = x.size();
            for (int i = 0; i < len; ++i) {
                int v = x[i] - 'a';
                if (!ch[u][v]) ch[u][v] = ++tot;
                u = ch[u][v];
            }
            ed[u] = 1;
        }//插入 
        inline void topoSort() {
            for (; !q.empty(); q.pop());
            for (int i = 0; i < 26; ++i)
                if (!in[i]) q.push(i);
            for (; !q.empty(); ) {
                int u = q.front();
                q.pop();
                for (int v = 0; v < 26; ++v)
                    if (e[u][v]) {
                        --in[v];
                        if (!in[v]) q.push(v);
                    }
            }
        }//拓扑排序 
        inline bool find(string x) {
            int u = 1, len = x.size();
            memset(e, 0, sizeof (e));
            memset(in, 0, sizeof (in));
            for (int i = 0; i < len; ++i) {
                if (ed[u]) return 0;//是其它字符串的前缀,无解
                int v = x[i] - 'a';
                for (int j = 0; j < 26; ++j)
                    if (v != j && ch[u][j] && !e[v][j]) {
                        e[v][j] = 1;//与同一层其它字母连边 
                        ++in[j];//统计入度 
                    }
                u = ch[u][v];
            }
            topoSort();
            for (int i = 0; i < 26; ++i)
                if (in[i]) return 0;//存在环,无解 
            return 1;
        }//检验字符串 
    } tr;
    
    int main() {
        read(n);
        tr.tot = 1;
        for (int i = 1; i <= n; ++i) {
            cin >> s[i];
            tr.insert(s[i]);//插入到 Trie 中 
        }
        for (int i = 1; i <= n; ++i)
            if (tr.find(s[i])) {
                ++ans;//统计个数 
                ok[i] = 1;//标记合法字符串 
            }
        write(ans);
        putchar('\n');
        for (int i = 1; i <= n; ++i)
            if (ok[i]) cout << s[i] << '\n';
        return 0;
    }
    
    • 1

    信息

    ID
    2129
    时间
    1000ms
    内存
    125MiB
    难度
    5
    标签
    递交数
    0
    已通过
    0
    上传者