題意:http://acm.hdu.edu.cn/showproblem.php?pid=4323
對於每個詢問,輸出給定一列數裏面與詢問的數a編輯距離小於等於b的數。
解:
dp,編輯距離。
轉自Matrix67
除了字符串匹配、查找回文串、查找重複子串等經典問題以外,日常生活中我們還會遇到其它一些怪異的字符串問題。比如,有時我們需要知道給定的兩個字符串“有多像”,換句話說兩個字符串的相似度是多少。1965年,俄國科學家Vladimir
Levenshtein給字符串相似度做出了一個明確的定義叫做Levenshtein距離,我們通常叫它“編輯距離”。字符串A到B的編輯距離是指,只用插入、刪除和替換三種操作,最少需要多少步可以把A變成B。例如,從FAME到GATE需要兩步(兩次替換),從GAME到ACM則需要三步(刪除G和E再添加C)。Levenshtein給出了編輯距離的一般求法,就是大家都非常熟悉的經典動態規劃問題。
在自然語言處理中,這個概念非常重要,例如我們可以根據這個定義開發出一套半自動的校對系統:查找出一篇文章裏所有不在字典裏的單詞,然後對於每個單詞,列出字典裏與它的Levenshtein距離小於某個數n的單詞,讓用戶選擇正確的那一個。n通常取到2或者3,或者更好地,取該單詞長度的1/4等等。這個想法倒不錯,但算法的效率成了新的難題:查字典好辦,建一個Trie樹即可;但怎樣才能快速在字典裏找出最相近的單詞呢?這個問題難就難在,Levenshtein的定義可以是單詞任意位置上的操作,似乎不遍歷字典是不可能完成的。現在很多軟件都有拼寫檢查的功能,提出更正建議的速度是很快的。它們到底是怎麼做的呢?1973年,Burkhard和Keller提出的BK樹有效地解決了這個問題。這個數據結構強就強在,它初步解決了一個看似不可能的問題,而其原理非常簡單。
首先,我們觀察Levenshtein距離的性質。令d(x,y)表示字符串x到y的Levenshtein距離,那麼顯然:
1. d(x,y) = 0 當且僅當 x=y (Levenshtein距離爲0 <==> 字符串相等)
2. d(x,y) = d(y,x) (從x變到y的最少步數就是從y變到x的最少步數)
3. d(x,y) + d(y,z) >= d(x,z) (從x變到z所需的步數不會超過x先變成y再變成z的步數)
最後這一個性質叫做三角形不等式。就好像一個三角形一樣,兩邊之和必然大於第三邊。給某個集合內的元素定義一個二元的“距離函數”,如果這個距離函數同時滿足上面說的三個性質,我們就稱它爲“度量空間”。我們的三維空間就是一個典型的度量空間,它的距離函數就是點對的直線距離。度量空間還有很多,比如Manhattan距離,圖論中的最短路,當然還有這裏提到的Levenshtein距離。就好像並查集對所有等價關係都適用一樣,BK樹可以用於任何一個度量空間。
建樹的過程有些類似於Trie。首先我們隨便找一個單詞作爲根(比如GAME)。以後插入一個單詞時首先計算單詞與根的Levenshtein距離:如果這個距離值是該節點處頭一次出現,建立一個新的兒子節點;否則沿着對應的邊遞歸下去。例如,我們插入單詞FAME,它與GAME的距離爲1,於是新建一個兒子,連一條標號爲1的邊;下一次插入GAIN,算得它與GAME的距離爲2,於是放在編號爲2的邊下。再下次我們插入GATE,它與GAME距離爲1,於是沿着那條編號爲1的邊下去,遞歸地插入到FAME所在子樹;GATE與FAME的距離爲2,於是把GATE放在FAME節點下,邊的編號爲2。
查詢操作異常方便。如果我們需要返回與錯誤單詞距離不超過n的單詞,這個錯誤單詞與樹根所對應的單詞距離爲d,那麼接下來我們只需要遞歸地考慮編號在d-n到d+n範圍內的邊所連接的子樹。由於n通常很小,因此每次與某個節點進行比較時都可以排除很多子樹。
舉個例子,假如我們輸入一個GAIE,程序發現它不在字典中。現在,我們想返回字典中所有與GAIE距離爲1的單詞。我們首先將GAIE與樹根進行比較,得到的距離d=1。由於Levenshtein距離滿足三角形不等式,因此現在所有離GAME距離超過2的單詞全部可以排除了。比如,以AIM爲根的子樹到GAME的距離都是3,而GAME和GAIE之間的距離是1,那麼AIM及其子樹到GAIE的距離至少都是2。於是,現在程序只需要沿着標號範圍在1-1到1+1裏的邊繼續走下去。我們繼續計算GAIE和FAME的距離,發現它爲2,於是繼續沿標號在1和3之間的邊前進。遍歷結束後回到GAME的第二個節點,發現GAIE和GAIN距離爲1,輸出GAIN並繼續沿編號爲0到2的邊遞歸下去(那條編號爲4的邊連接的子樹又被排除掉了,在這個圖中沒有編號爲0的邊)……
實踐表明,一次查詢所遍歷的節點不會超過所有節點的5%到8%,兩次查詢則一般不會17-25%,效率遠遠超過暴力枚舉。適當進行緩存,減小Levenshtein距離常數n可以使算法效率更高。
#include <cstdio>
#include <cstring>
const int MAXN = 1505;
int dp[15][15];
char magic[MAXN][15];
int len[MAXN];
char temp[15];
inline int min(const int &x, const int &y)
{
return x < y ? x : y;
}
inline int min(const int &x, const int &y, const int &z)
{
return min(x, min(y, z));
}
int main()
{
int t, n, m;
scanf("%d", &t);
for(int cas=1;cas<=t;++cas)
{
scanf("%d%d", &n, &m);
for(int i=0;i<n;++i)
{
scanf("%s", magic[i]);
len[i] = strlen(magic[i]);
}
printf("Case #%d:\n", cas);
int threshold;
for(int i=0;i<m;++i)
{
scanf("%s%d", temp, &threshold);
int ans = 0;
int l = strlen(temp);
for(int j=0;j<n;++j)
{
for(int k=0;k<=l;++k)
{
dp[0][k] = k;
}
for(int k=0;k<=len[j];++k)
{
dp[k][0] = k;
}
for(int ii=0;ii<len[j];++ii)
{
for(int jj=0;jj<l;++jj)
{
dp[ii+1][jj+1] = min(dp[ii][jj+1] + 1, dp[ii+1][jj] + 1, dp[ii][jj] + (magic[j][ii] == temp[jj] ? 0 : 1));
}
}
if(dp[len[j]][l] <= threshold)
{
++ ans;
}
}
printf("%d\n", ans);
}
}
return 0;
}
BK樹模板:
class BK {
int maxn = 12;// 最大距離
class node {
node ch[];
String key;
int count;
node(String s) {
key = s;
count = 1;
ch = new node[maxn];
}
}
node root;
int dp[][] = new int[maxn][maxn];
int dis(String a, String b) {
int n = a.length();
int m = b.length();
for (int i = 0; i < maxn; i++)
dp[0][i] = dp[i][0] = i;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
int flag = 1;
if (a.charAt(i - 1) == b.charAt(j - 1))
flag = 0;
dp[i][j] = Math.min(
Math.min(dp[i - 1][j], dp[i][j - 1])+1,
dp[i - 1][j - 1] + flag);
}
}
return dp[n][m];
}
void init() {
root = null;
}
void insert(String s, node r) {
int d = dis(r.key, s);
if (d == 0){
r.count++;
}
else if(r.ch[d]==null)
r.ch[d]=new node(s);
else
insert(s, r.ch[d]);
}
void insert(String s) {
if(root==null)
root= new node(s);
else
insert(s, root);
}
int query(String s, int n) {
return query(root, s, n);
}
int query(node r, String s, int n) {
int ans = 0;
if (r == null)
return 0;
int d = dis(r.key, s);
if (d <= n)
ans += r.count;
int a = Math.max(d - n, 1), b = Math.min(maxn-1, d + n);
for (int i = a; i <= b; i++)
ans += query(r.ch[i], s, n);
return ans;
}
}