1. 程式人生 > >LOJ2083 [NOI2016] 優秀的拆分 【哈希】【調和級數】

LOJ2083 [NOI2016] 優秀的拆分 【哈希】【調和級數】

|| 最長前綴 noi 問題 -s base != return ==

題目分析:

好題!我們發現題目實際是要求出從某個左端點開始跑出去的BB型有多少個和從某個右端點開始跑出去的AA型有多少個。

發現這個問題是對稱的,所以只考慮從左端點跑出去的BB型有多少個就可以了。

我們不妨考慮長度為$k$的BB型,那麽我們把字符串每$k$個化成一個整體,然後如果從$i$開始存在一個長度為$k$的BB型,就等價於$i$開始這個整體的後綴等於下一個整體的後綴,下一個整體的後綴等於下下個整體的前綴,所以我們用哈希來求出最長後綴和最長前綴就可以做了。

代碼:

 1 #include<bits/stdc++.h>
 2 using namespace std;
3 4 const int maxn = 102000; 5 6 int n; 7 char str[maxn]; 8 int pre[maxn],suf[maxn]; // longest qianzhui longest houzhui 9 int f[maxn],g[maxn]; 10 11 namespace HASH{ 12 int ph[2][maxn],bs[2][maxn]; 13 const int base = 37; 14 const int mod1 = 1004535809,mod2 = 999911659; 15 void
buildhash(){ 16 ph[0][1] = ph[1][1] = str[0]-a+1; 17 bs[0][0] = bs[1][0] = 1; 18 for(int i=1;i<n;i++){ 19 ph[0][i+1] = (1ll*base*ph[0][i]+str[i]-a+1)%mod1; 20 ph[1][i+1] = (1ll*base*ph[1][i]+str[i]-a+1)%mod2; 21 } 22 for(int i=1;i<=n;i++) bs[0][i]=1ll*bs[0
][i-1]*base%mod1; 23 for(int i=1;i<=n;i++) bs[1][i]=1ll*bs[1][i-1]*base%mod2; 24 } 25 int pd(int l1,int r1,int l2,int r2){ 26 int z1=ph[0][r1+1]-1ll*bs[0][r1-l1+1]*ph[0][l1]%mod1;if(z1<0)z1+=mod1; 27 int z2=ph[1][r1+1]-1ll*bs[1][r1-l1+1]*ph[1][l1]%mod2;if(z2<0)z2+=mod2; 28 int y1=ph[0][r2+1]-1ll*bs[0][r2-l2+1]*ph[0][l2]%mod1;if(y1<0)y1+=mod1; 29 int y2=ph[1][r2+1]-1ll*bs[1][r2-l2+1]*ph[1][l2]%mod2;if(y2<0)y2+=mod2; 30 if(z1 == y1 && z2 == y2) return true; 31 else return false; 32 } 33 int maxlen(int st1,int st2,int dr){ 34 if(st2 >=n) return 0; 35 int tl=1,tr=(dr==0?st1+1:n-st2+1); 36 if(str[st1] != str[st2]) return 0; 37 while(tl < tr){ 38 int mid = (tl+tr+1)/2; 39 int l1,r1,l2,r2; 40 if(dr == 0){r1=st1,r2=st2;l1=st1-mid+1,l2=st2-mid+1;} 41 else{l1=st1,l2=st2;r1=st1+mid-1,r2=st2+mid-1;} 42 if(pd(l1,r1,l2,r2)) tl = mid; 43 else tr = mid-1; 44 } 45 return tl; 46 } 47 } 48 49 50 void init(){ 51 memset(pre,0,sizeof(pre)); 52 memset(suf,0,sizeof(suf)); 53 memset(HASH::ph,0,sizeof(HASH::ph)); 54 memset(f,0,sizeof(f)); 55 memset(g,0,sizeof(g)); 56 } 57 58 void work(){ 59 HASH::buildhash(); 60 for(int i=1;i<=n/2;i++){ 61 int k = 0; 62 for(int j=0;j<n;j+=i){ 63 k++; 64 if(j+i < n){pre[k] = min(i,HASH::maxlen(j,j+i,1));} 65 if(j-i >=0){suf[k] = min(i,HASH::maxlen(j-1,j+i-1,0));} 66 } 67 for(int j=2,st=i;j<=k;j++,st+=i){ 68 if(pre[j] + suf[j] < i || suf[j] == 0) continue; 69 int l = st-suf[j],r = min(st-1,(st-i)+pre[j]); 70 f[l]++; f[r+1]--; 71 } 72 for(int j=1,st=0;j<k;j++,st+=i){ 73 if(pre[j] + suf[j] < i || pre[j] == 0) continue; 74 int l = max(st+i,(st+i-1+(i-suf[j]))),r = (st+i-1+pre[j]); 75 g[l]++; g[r+1]--; 76 } 77 for(int j=1;j<=k;j++) pre[j] = suf[j] = 0; 78 } 79 for(int i=1;i<n;i++) f[i] = f[i-1] + f[i]; 80 for(int i=1;i<n;i++) g[i] = g[i-1] + g[i]; 81 long long ans = 0; 82 for(int i=1;i<n;i++){ans += 1ll*f[i]*g[i-1];} 83 printf("%lld\n",ans); 84 } 85 86 int main(){ 87 int Tmp; scanf("%d",&Tmp); 88 while(Tmp--){ 89 init(); 90 scanf("%s",str); 91 n = strlen(str); 92 work(); 93 } 94 return 0; 95 }

LOJ2083 [NOI2016] 優秀的拆分 【哈希】【調和級數】