1. 程式人生 > >hdu 4468 spy 極其精彩的一道kmp靈活運用題

hdu 4468 spy 極其精彩的一道kmp靈活運用題

sum pen new 最短 agent 描述 數組 ... open

出的超級好的一道題。至於好在哪裏,請思考題目:

題意抽象出來為給定一個字符串r,找出它的一個最短後綴s,使得這個r可以被 s的某前綴+s的某前綴+......+s的某前綴+s本身構造出來。

具體題目描述如下:

“Be subtle! Be subtle! And use your spies for every kind of business. ”
— Sun Tzu
“A spy with insufficient ability really sucks”
— An anonymous general who lost the war
You, a general, following Sun Tzu’s instruction, make heavy use of spies and agents to gain information secretly in order to win the war (and return home to get married, what a flag you set up). However, the so-called “secret message” brought back by your spy, is in fact encrypted, forcing yourself into making deep study of message encryption employed by your enemy.
Finally you found how your enemy encrypts message. The original message, namely s, consists of lowercase Latin alphabets. Then the following steps would be taken:
* Step 1: Let r = s
* Step 2: Remove r’s suffix (may be empty) whose length is less than length of s and append s to r. More precisely, firstly donate r[1...n], s[1...m], then an integer i is chosen, satisfying i ≤ n, n - i < m, and we make our new r = r[1...i] + s[1...m]. This step might be taken for several times or not be taken at all.
What your spy brought back is the encrypted message r, you should solve for the minimal possible length of s (which is enough for your tactical actions).

輸入:

There are several test cases.
For each test case there is a single line containing only one string r (The length of r does not exceed 10 5). You may assume that the input contains no more than 2 × 10 6 characters.
Input is terminated by EOF.

輸出:

For each test case, output one line “Case X: Y” where X is the test case number (starting from 1) and Y is the desired answer.

樣例:

abc
aab
abcadabcabcad
aaabbbaaaabbbaa
abcababcd

樣例答案:
Case 1: 3
Case 2: 2
Case 3: 5
Case 4: 6
Case 5: 4


題解:
這題初看很容易只能想到枚舉答案長度i,從r後面截長度為i的後綴然後暴力匹配是否滿足條件。復雜度顯然太高無法接受。
之後如果運用動態規劃的思想,可以對r[1...k]考慮子問題。
但是會發現由於要加一個s本身,考慮完全同質的子問題有點問題。
於是考慮r[1...k]可以表示為s1某前綴+s1某前綴+...+s1某前綴時s1的最短長度
那麽對於r[1...k+1],
如果r[k+1]可以成為s1的某前綴的一部分或者自己成為s1的前綴。s1就仍然滿足r[1....k+1]的要求,最短長度不變;
如果不可以,那麽理論上把s1的末尾添加字符r[k+1]便滿足了r[1...k+1]的要求。但是這時得考慮下最後得有一個s本身的問題。
所以添加字符的時候不能只加r[k+1],應該加一段字符。從r[1...k+1]中上一次模板串跟r[1..k+1]的一部分完全匹配的地方開始加,加到r[k+1]。
一直掃描完題意給的整個r。最後再給一直維護的模板串加一段:最後一次完全匹配的地方到r的末尾的字符。
以上是思考過程。

以下是更加嚴謹的題解敘述:
從頭開始掃描r,始終維護一個模板串s和上一次完全匹配位置的標記mark:
在r[i]處有三種操作:
1.若在r[i]處成功進行kmp匹配,則模板串不變
2.如匹配失敗,動態添加模板串,添加內容為位置為mark至i的子串
3.如果進行了一次完全匹配,更新mark。

 1 #include<cstdio>
 2 #include<cstring>
 3 #define rep(i,a,b) for(int i=a;i<=b;++i)
 4 using namespace std;
 5 const int MAXN=110000;
 6 char s[MAXN];
 7 char ans[MAXN];
 8 int next[MAXN];
 9 int tot;
10 int main()
11 {
12     //freopen("in.txt","r",stdin);
13     int cnt=0;
14     while(scanf("%s",s)!=EOF)
15     {
16         int len=strlen(s);
17         tot=0;
18         ans[tot++]=s[0];            //將維護的模板串初始化
19         next[0]=0;
20         int mark=0;                 //mark標記上一次完全匹配的位置
21         for(int i=0,k=0;i<len;++i)
22         {
23             while(k>0&&s[i]!=ans[k])        //嘗試s[i]是否能成為維護的模板串前綴的一部分
24             {
25                 k=next[k-1];
26             }
27             if(s[i]==ans[k]) k++;
28             else if(k==0)                   //嘗試失敗
29             {
30                 for(int j=mark+1;j<=i;++j)  //更新模板串
31                 {
32                     ans[tot++]=s[j];
33                     int tmp=next[tot-2];            //模板串的next數組需要跟著動態更新
34                     while(tmp>0&&ans[tmp]!=ans[tot-1]) tmp=next[tmp-1];
35                     if(ans[tmp]==ans[tot-1]) tmp++;
36                     next[tot-1]=tmp;
37                 }
38                 mark=i;
39             }
40             if(k==tot) mark=i,k=next[tot-1];        //進行了一次完全匹配,更新mark,並將k跳回
41         }
42         for(int j=mark+1;j<len;++j) ans[tot++]=s[j];
43         printf("Case %d: %d\n",++cnt,tot);
44     }
45     return 0;
46 }

做出來之後發現這道題考察到了kmp算法的所有操作。但是需要人將其kmp算法的各個操作有機地拆開與重組,來解決這個新的問題。
非常有助於加深對kmp的理解。

hdu 4468 spy 極其精彩的一道kmp靈活運用題