洛谷P4407 [JSOI2009] 电子字典 题解
题意:人们在英文字典中查找某个单词的时候可能不知道该单词的完整拼法,而只知道该单词的一个错误的近似拼法,这时人们可能陷入困境,为了查找一个单词而浪费大量的时间。带有模糊查询功能的电子字典能够从一定程度上解决这一问题:用户只要输入一个字符串,电子字典就返回与该单词编辑距离最小的几个单词供用户选择。
字符串a与字符串 \(b\) 的编辑距离是指:允许对 \(a\) 或 \(b\) 串进行下列“编辑”操作,将 \(a\) 变为 \(b\) 或 \(b\) 变为 \(a\) ,最少“编辑”次数即为距离。
- 删除串中某个位置的字母;
- 添加一个字母到串中某个位置;
- 替换串中某一位置的一个字母为另一个字母;
给定 \(n\) 个不同的单词,\(m\) 次询问,对于一个待查询字符串,如果它是单词,则返回 \(-1\);如果它不是单词,则返回字典中有多少个单词与它的编辑距离为 \(1\) 。
\(n,m \le 10,000\) ,每个单词长度 \(\le 20\) ,每个待查询字符串长度 \(\le 20\)
首先建 \(\tt{trie}\) 树很容易想到
注意到这个数据范围很小,尝试在 \(\tt{trie}\) 树上暴搜。
显然我们需要记录的状态有:
- 当前所在结点 \(u\)
- 当前枚举的字符串长度 \(l\) (实际上并不完全是,只是判断长度用的)
- 是否使用修改 \(\text{ck}=0/1\) ( \(0\) 表示没有使用修改)
下面是算法分析,比较抽象,建议直接看代码。(所以为什么我要写
方便起见,记当前字符为 \(s_l\)
当 \(\text{ck}=1\) 时,如果 \(u\) 存在字符为 \(s_l\) 的子结点,直接搜。
当 \(\text{ck}=0\) 时,除了搜子结点,还需要考虑每个操作的处理
- 操作1:删除某个字符。直接搜 \(l+1\) 就好了
- 操作2:增加某个字符。直接枚举字符,搜 \(l\) 就好了
- 操作3:替换某个字符。枚举一个不同于 \(s_{l+1}\) 的字符,搜 \(l+1\)
注意去重!!!!
因为 \(\tt{abc}\) 在 \(\tt{a}\) 前后增加一个 \(\tt{a}\) ,都会变成 \(\tt{aabc}\) !!!
时间复杂度分析
比较有趣。
对于一个长度为 \(20\) 的串,思考我们至多枚举了多少修改串
一共有 \(21\) 个位置可添加字符,\(20\) 个字符可替换,\(20\) 个字符可删除,则 \[ (21+20) \times 26 + 20 = 1086 \] \(\tt{trie}\) 树树高至多在 \(20\) ,\(m\le 10^4\)
则粗略算一下,计算量为 \[ 1086 \times 2\times 10^5 = 2.172\times 10^8 \] 当然这个估计显然是过于大了。
因为很多枚举的修改串拥有相同前缀
也就是说,这些串并不是每个都要从上到下跑 \(\tt{trie}\) 树的
而且我们有很多的修改串会被剪掉,这样看来完全可过
强行写个时间复杂度,那可能是 \(O(\max\{s_i\}\times\sum |s_i|^2)\) 吧
代码:
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <iomanip>
#include <random>
using namespace std;
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
#define N (int)(2e5+15)
int n,m,vis[N],q[N],pos;
string s;
struct Trie
{
int tot,trie[N][26],ori,len,ed[N];
void insert(string s)
{
int u=0;
for(int i=0; i<s.size(); i++)
{
int c=s[i]-'a';
if(!trie[u][c])trie[u][c]=++tot;
u=trie[u][c];
}
ed[u]=1;
}
void dfs(int u,int l,int ck)
{
if(l==len&&ed[u]&&!ck)
return ori=1,void(0);
if(l==len&&ed[u])
{
if(!vis[u]) vis[q[++pos]=u]=1;
return ;
}
int c=s[l]-'a';
if(!ck)
{
if(l<len)dfs(u,l+1,1);
for(int i=0; i<26; i++)
if(trie[u][i])
{
dfs(trie[u][i],l,1);
if(i!=c)dfs(trie[u][i],l+1,1);
}
}
if(l==len)return;
if(trie[u][c])dfs(trie[u][c],l+1,ck);
}
}tr;
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
// freopen("check.in","r",stdin);
// freopen("check.out","w",stdout);
cin >> n >> m;
for(int i=1; i<=n; i++)
cin >> s,tr.insert(s);
for(int i=1; i<=m; i++)
{
cin >> s; tr.ori=0; tr.len=s.size(); tr.dfs(0,0,0);
if(tr.ori)cout << "-1\n";
else cout << pos << '\n';
for(int i=1; i<=pos; i++)
vis[q[i]]=0;
pos=0;
}
return 0;
}