最新文章

这里会显示最新的几篇文章摘要。

电子字典(字典树)

P4407 [JSOI2009] 电子字典

题目描述

人们在英文字典中查找某个单词的时候可能不知道该单词的完整拼法,而只知道该单词的一个错误的近似拼法,这时人们可能陷入困境,为了查找一个单词而浪费大量的时间。带有模糊查询功能的电子字典能够从一定程度上解决这一问题:用户只要输入一个字符串,电子字典就返回与该单词编辑距离最小的几个单词供用户选择。

字符串 \(a\) 与字符串 \(b\) 的编辑距离是指:允许对 \(a\)\(b\) 串进行下列“编辑”操作,将 \(a\) 变为 \(b\)\(b\) 变为 \(a\),最少“编辑”次数即为距离。

  1. 删除串中某个位置的字母;
  2. 添加一个字母到串中某个位置;
  3. 替换串中某一位置的一个字母为另一个字母。

JSOI 团队正在开发一款电子字典,你需要帮助团队实现一个用于模糊查询功能的计数部件:对于一个待查询字符串,如果它是单词,则返回 \(-1\);如果它不是单词,则返回字典中有多少个单词与它的编辑距离为 \(1\)

输入格式

第一行包含两个正整数 \(N\)\(M\)

接下来的 \(N\) 行,每行一个字符串,第 \(i+1\) 行为单词 \(W_i\),单词长度在 \(1\)\(20\) 之间。

再接下来 \(M\) 行,每行一个字符串,第 \(i+N+1\) 表示一个待查字符串 \(Q_i\)。待查字符串长度在 \(1\)\(20\) 之间。\(W_i\)\(Q_i\) 均由小写字母构成,文件中不包含多余空格。

输出格式

输出应包括 \(M\) 行,第 \(i\) 行为一个整数 \(X_i\)

  • \(X_i = -1\) 表示 \(Q_i\) 为字典中的单词;

  • 否则 \(X_i\) 表示与 \(Q_i\) 编辑距离为 \(1\) 的单词的个数。

输入输出样例 #1

输入 #1

4 3
abcd
abcde
aabc
abced
abcd
abc
abcdd

输出 #1

-1
2
3

说明/提示

样例解释

  • abcd 在单词表中出现过;
  • abc 与单词 abcdaabc 的编辑距离都是 \(1\)
  • abcdd 与单词 abcdabcdeabced 的编辑距离都是 \(1\)

数据范围与约定

  • 所有单词互不相同,但是查询字符串可能有重复;
  • \(50\%\) 的数据范围,\(N,M\le 10^3\)
  • \(100\%\) 的数据范围,\(N,M\le 10^4\)

分析:

一部分单词存在字典树树中,查询一个字符串有几个单词可以通过增添,替换,删除一个字符的一个操作得到。

可以看作是对查询的字符串进行一个操作,查询字典树是否有这个单词。使用dfs进行搜索,首先需要考虑dfs的参数

  1. 现在操作的字符在字典树的哪个位置,因此需要变量p
  2. 处理到了字符串的哪个字符,需要标明字符串位置lenth
  3. 是否有已经编辑过一次字符串了 f
    所以使用void dfs(int p,int lenth,int f)

操作:

  1. 删除,直接相当于跳过该字符:dfs(p, lenth+1, 1)
  2. 替换,可以使用循环遍历26个字母,在son[p][i]存在的情况下搜索下一个字符dfs(son[p][i], lenth+1, 1)
  3. 增添,也是遍历26个字母,可以和替换一起,dfs(son[p][i], lenth, 1)
  4. 此外
#include <bits/stdc++.h>
//#define int long long
using namespace std;
const int N = 2e5 + 10;
int n,m,son[N][26],cnt[N],idx,lens,vis[N],ans,flag;     //vis记录单词是否已经到达过,一样的单词不能算两个,别忘了每次查询清空!
char s[22];                                        
inline void ins(char s[]) {                             //插入字典树
    int len = strlen(s),p = 0;
    for (int i = 0;i < len;++i) {
        int t = s[i] - 'a';
        while (!son[p][t]) son[p][t] = ++idx;
        p = son[p][t];
    }
    cnt[p]++;                                          //标记单词末尾

}

void dfs(int p,int lenth,int f) {
    if ( lenth == lens && cnt[p] && !f) {             //对于直接得到的单词
        cout << -1 << '\n';
        flag = 1;
        return;
    }

    if (lenth == lens && cnt[p] && !vis[p]) {         //对于修改后得到的单词
        vis[p] = 1;
        ans++;
        return;
    }

    if (!f) {                                           //在没有改动过的前提下
        if (lenth < lens) dfs(p,lenth+1,1);          //删除一个字符,注意删除条件
        for (int i = 0;i < 26;++i) {
           if (son[p][i]) dfs(son[p][i],lenth,1);     //增添操作
            if (i != s[lenth]-'a' && son[p][i]) dfs(son[p][i],lenth+1,1); //替换操作,注意i对应是s[lenth] - 'a',感觉i != s[lenth] - 'a'没用,确实可以ac,但是如果替换自己会发生什么?相当于没换,浪费了修改机会,但这种情况会在其他情况出现。还是留着保险
        }
    }
    if (lenth == lens) return;                          //前面循环已经有过增添了,所以不用担心在末尾增添加成为一个单词的情况
    if (son[p][s[lenth] - 'a']) dfs(son[p][s[lenth] - 'a'],lenth+1,f);  //正常查询的情况

}
signed main() {
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);

    cin >> n >> m;
    while (n--) {
        cin >> s;
        ins(s);
    }

    while (m--) {
        memset(vis,0,sizeof(vis));    //对每次查询都要重置标记数组
        cin >> s;
        lens = strlen(s);     
        ans = 0,flag = 0;
        dfs(0,0,0);
        if (!flag) cout << ans << '\n';

    }

}
posted @ 2025-02-27 22:35  bakul  阅读(21)  评论(0)    收藏  举报