hdu - 4323 - Magic Number - dp + 數據結構優化

題意: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;
        }
    }


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章