1. 程式人生 > >【CodeForces】961 F. k-substrings 字符串哈希+二分

【CodeForces】961 F. k-substrings 字符串哈希+二分

problem str ns2 def 哈希表 AS check 技術分享 log

【題目】F. k-substrings

【題意】給定長度為n的串S,對於S的每個k-子串$s_ks_{k+1}...s_{n-k+1},k\in[1,\left \lceil \frac{n}{2} \right \rceil]$,找到滿足[奇數長度][嚴格子串][同時是前綴和後綴]的最長子串。n<=10^6。

【算法】字符串哈希+二分

【題解】任意兩個對應子串,它們有一個不變量——它們的中心一定是i和n-i+1。而且固定中心之後,能延伸的最長相等子串是可以二分+哈希得到的。

所以枚舉k,二分+哈希處理出以k為中心和對應串相等的最長子串半長L。

然後實際上是一個遞減序列覆蓋求單點最值的問題,有一個巧妙的解決方法,在k-L+1處標記答案,然後從前往後掃描每次和ans[i]=max{ans[i],ans[i-1]-2

}。因為這是一個從大到小的遞減序列,所以就不需要考慮終止,因為<0就自然沒有意義了。

復雜度O(n log n)。

技術分享圖片
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=1000010;
int ans[maxn],n;
char s[maxn];
int a[maxn],b[maxn],c[maxn],d[maxn];
const int MOD1=993258975,MOD2=934384734,base1=233,base2=197
; bool check(int l,int r,int L,int R){ int x=r-l+1;// int ans1=(a[r]-1ll*a[l-1]*c[x]%MOD1+MOD1)%MOD1; int ans2=(a[R]-1ll*a[L-1]*c[x]%MOD1+MOD1)%MOD1; if(ans1!=ans2)return 0; ans1=(b[r]-1ll*b[l-1]*d[x]%MOD2+MOD2)%MOD2; ans2=(b[R]-1ll*b[L-1]*d[x]%MOD2+MOD2)%MOD2; if(ans1!=ans2)return
0; return 1; } int main(){ scanf("%d%s",&n,s+1);c[0]=d[0]=1; for(int i=1;i<=n;i++)a[i]=(1ll*a[i-1]*base1+s[i])%MOD1,b[i]=(1ll*b[i-1]*base2+s[i])%MOD2; for(int i=1;i<=n;i++)c[i]=1ll*c[i-1]*base1%MOD1,d[i]=1ll*d[i-1]*base2%MOD2; memset(ans,-1,sizeof(ans)); for(int i=1;i<=n/2;i++){ int l=1,r=i+1,mid; while(l<r){ mid=(l+r)>>1; if(check(i-mid+1,i+mid-1,n-i+1-mid+1,n-i+1+mid-1))l=mid+1;else r=mid; } l--; ans[i-l+1]=max(ans[i-l+1],2*l-1); } for(int i=1;i<=n/2;i++){ ans[i]=max(ans[i],ans[i-1]-2); printf("%d ",ans[i]); } if(n&1)printf("-1"); return 0; }
View Code

字符串哈希:將字符串換算成base進制的數字取模接近int的素模數,比較兩段字符串時判斷a[r]-a[l-1]*base^(r-l+1)是否相等即可。當然需要雙哈希。

如果是建哈希表,就建一條鏈存真實值。(參考插頭DP)

順便提一下樸素KMP處理一次詢問的方法:整個字符串跑出fail數組,那麽從n一直fail到最小的>0的位置就是最小首尾匹配串了。KMP的fail樹十分強大。

【CodeForces】961 F. k-substrings 字符串哈希+二分