1. 程式人生 > >【NOI2014】動物園

【NOI2014】動物園

col pan round 字符 ++ 要求 blog 滿足 獲取

這道題因為自己把1000000007寫成了100000007而浪費了三個小時,所以告誡自己:數一下幾位!數一下幾位!數一下幾位!

先貼代碼~

 1 #include<cstdio>
 2 #include<cstring>
 3 #define mod 1000000007 //該看眼科了... 
 4 #define LL long long
 5 int next[1000005],cnt[1000005],n;
 6 LL ans;                //cnt[i]表示前i個字符公共前後綴的數目,包括自己!也就是說cnt[i]至少為1(i!=0)(至於為什麽要這樣做,在(2)中解釋)
 7 char s[1000005];
 8 void
getcnt(int l){ //getcnt:獲取常規的next[],同時得到cnt[] ——(#) 9 next[0]=-1; 10 cnt[0]=0; 11 int i=0,k=-1; 12 while(i<l){ 13 while(k>=0&&s[k]!=s[i]) k=next[k]; 14 i++,k++; 15 next[i]=k; 16 cnt[i]=cnt[k]+1; 17 } 18 } 19 void solve(int l){ //solve:大體上是再求一遍next[]的過程,每次可以得到要求的num[],這裏有兩個問題:
20 ans=1; //1.為什麽不能在最外層while每次開始的時候用k=next[i],而要再像求next[]那樣求一遍,直接用不會更快嗎? ——(1) 21 int k=-1,i=0; 22 while(i<l){ //2.num[i]是怎樣通過cnt[i]得到的? ——(2) 23 while(k>=0&&s[k]!=s[i]) k=next[k]; 24 k++,i++; 25 while(2*k>i) k=next[k]; 26 ans=ans*(LL)(cnt[k]+1
)%mod; 27 } 28 printf("%lld\n",ans); 29 } 30 int main() 31 { 32 scanf("%d",&n); 33 while(n--){ 34 scanf("%s",s); 35 int l=strlen(s); 36 getcnt(l); 37 solve(l); 38 } 39 }

現在來一一解釋上面的三個地方。

題目要求的是數量,自然就可以想到next[]數組的意義:最長公共前後綴長度,也就是next[i]即前i個字符的最長公共前後綴長度。

現在我們假定對i已經知道了next[i](現在只把它看成一個數字),註意到前i個字符的前next[i]個和後next[i]個字符是相等的,我們要求的是所有的公共前後綴,既然next[i]是最長的一個,又註意到next[next[i]]是前next[i]個字符的最長公共前後綴,從而我們可以推出,前i個字符中的前next[next[i]]和後next[next[i]]個字符也是相同的。也就說,我們可以遞推地求解前i個字符的公共前後綴數量,由於next[]數組的最長性質,我們保證一定可以取遍所有的公共前後綴。實際上這也是用next[]數組求解周期串問題的核心思想。

現在我們可以解答(#)了,假設我們已經知道k=next[i](始終把next[i]看成一個數字,就像定積分是一個數字一樣(逃...)和前i-1個字符每個字符的cnt[]值(這個是我們遞推的依據),從而可以得到:

cnt[i]=cnt[k]+1


cnt[k]是好理解的,就是前k個字符的所有公共前後綴數量(也就是我們現在查考的前i個字符的最長公共前後綴[0,...,k-1]的公共前後綴數量),由上面的分析我們知道了,前k個字符的公共前後綴就是前i個字符的公共前後綴,再加上前i個字符本身(為什麽要加自己?這樣不就不滿足長度不超過i/2了嗎?這個問題留到(2)解答),就得到了上面的遞推式,從而可以通過初始化cnt[0]=0(顯然不存在前0個字符這種說法的嘛)而得到所有的cnt[i],1<=i<=strlen(s)

下面來解答(1)和(2)這兩個問題。

未完待 續……

【NOI2014】動物園