Trie树集训

Trie树集训

[P3879 TJOI2010]阅读理解 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

字典树裸题

给你一个文章,让你求出每个单词所属的句子。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <bitset>

using namespace std;
const int N = 1e6 + 50, M = 5e5 + 50;
int son[N][26], n, m, cnt, q;
char s[1010];
bitset<1101>b[M];

void insert(char *s, int pos)
{
    int p = 0;
    for(int i = 0; s[i] != '\0'; i++)
    {
        int x = s[i] - 'a';
        if(!son[p][x]) son[p][x] = ++cnt;
        p = son[p][x];
    }
    b[p][pos] = 1;
}

void query(char *s)
{
    int p = 0;
    for(int i = 0; s[i] != '\0'; i++)
    {
        int x = s[i] - 'a';
        if(!son[p][x])
        {
            printf("\n");
            return;
        }
        p = son[p][x];
    }
    for(int i = 1; i <= n; i++)
    {
        if(b[p][i] == 1) printf("%d ", i);
    }
    printf("\n");
}

int main()
{
    scanf("%d", &n);
    for(int i = 1; i <= n; i++)
    {
        scanf("%d", &m);
        for(int j = 1; j <= m; j++)
        {
            scanf("%s",s);
            insert(s, i);
        }
    }
    scanf("%d", &q);
    while(q--)
    {
        scanf("%s", s);
        query(s);
    }
    return 0;
}

[P2292 HNOI2004]L语言 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

Trie加上记忆化搜索

枚举长字符串的时候,注意把每一次询问的结果进行存储。

这样可以保证时间复杂度满足要求。

就是隔一段打个标记,代表最远可以匹配到哪里,但是要注意一点:每次匹配单词都要从字典树的根节点开始匹配,如果匹配到了这个单词的末尾,就在当前位置打个标记,也就是说我们需要两重循环枚举,第一重循环枚举当前记录答案的位置,第二重循环枚举最远可以到达什么地方。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <map>

using namespace std;
const int N = 2e6 + 60;
char s[N];
int son[N][26], id;
bool flag[N], vis[N];
int n, m, ans;
map<string, bool>m1;
map<string, int>m2;

void insert(char *s)
{
    int p = 0;
    for(int i = 0; s[i] != '\0'; i++)
    {
        int x = s[i] - 'a';
        if(!son[p][x]) son[p][x] = ++id;
        p = son[p][x];
    }
    flag[p] = 1;
}

int query(char *s)
{
    if(m1[s]) return m2[s];
    int len = strlen(s + 1), p;
    memset(vis, 0, sizeof(vis));
    vis[0] = 1;

    for(int i = 0; i <= len; i++)
    {
        if(vis[i]) ans = i;
        else continue;
        p = 0;
        for(int j = i + 1; j <= len; j++)
        {
            int x = s[j] - 'a';
            if(!son[p][x])
            {
                break;
            }
            p = son[p][x];
            if(flag[p]) vis[j] = 1;
        }
    }
    m1[s] = true;
    m2[s] = ans;
    return ans;
}

int main()
{
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++)
    {
        scanf("%s", s);
        insert(s);
    }
    for(int i = 1; i <= m; i++)
    {
        scanf("%s", s + 1);
        int ans = query(s);
        printf("%d\n", ans);
    }

    return 0;
}

[P2922 USACO08DEC]Secret Message G - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

如果Trie无法走了,答案为res

否则为以当前的点为终点或以此点为前缀的点。

每一次查询的结果为 r e s − a n s [ p ] + s u m [ p ] res - ans[p] + sum[p] resans[p]+sum[p]

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;
const int N = 5e5 + 50, M = 1e4 + 50;
int n, m, id, cnt = 1;
char s[M];
int son[N][2], ans[N], sum[N];

void insert(char *s)
{
    int p = 1;
    for(int i = 0; i < id; i++)
    {
        int x = int(s[i] - '0');
        if(son[p][x] == -1) son[p][x] = ++cnt;
        p = son[p][x];
        sum[p] ++;
    }
    ans[p]++;
}

int query(char *s)
{
    int p = 1, res = 0;
    for(int i = 0; i < id; i++)
    {
        int x = int(s[i] - '0');
        if(son[p][x] == -1) return res;
        p = son[p][x];
        res += ans[p];
    }
    return res - ans[p] + sum[p];
}

int main()
{
    memset(son, -1, sizeof(son));
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++)
    {
        memset(s, '\0', sizeof(s));
        int x;
        scanf("%d", &x);
        id = 0;
        for(int i = 1; i <= x; i++)
        {
            int u;
            scanf("%d", &u);
            s[id++] = char(u + '0');
        }
        insert(s);
    }

    for(int i = 1; i <= m; i++)
    {
        memset(s, '\0', sizeof(s));
        int x;
        scanf("%d", &x);
        id = 0;
        for(int i = 1; i <= x; i++)
        {
            int u;
            scanf("%d", &u);
            s[id++] = char(u + '0');
        }
        int ans = query(s);
        printf("%d\n", ans);
    }

    return 0;
}

[P3065 USACO12DEC]First! G - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) (字典树上拓扑排序)

题意:重新排列字母表的顺序来计算输入中有哪些字符串可以排在第一个(即字典序最小)

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

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

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

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

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

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

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值