/* 记一串数字真难。 5435 今天比赛又是hjcAK的一天。 今天开题顺序是312,在搞T1之前搞了T3 昨天某谷月赛真是毒瘤。 但是讲评的同学不错,起码T4看懂了... 构造最优状态然后DP的思路真妙 */
Problem A lcp
给出字符串S,m个询问,每个询问含有$l1,r1,l2,r2$求|S|子串$[l1,r1]$和$[l2,r2]$的LCP(最长公共前缀)
对于100%的数据$ 1 \leq |S|,m \leq 10^5 , l1 \leq r1 ,l2 \leq r2$
考虑二分答案套字符串Hash,于是就不用KMP了(我不会KMP)
再说一下Hash的思路吧hash[i]表示S前i个字符的前缀哈希值,设基底为E=$31$,模数mo=$10^9+9$
令hash[0]=0;对于$i \geq 1 计算方法如下 :hash[i]=hash[i-1] \times E+Val(s[i]) $ Val(x)是一个映射把char类型的x映射成一个int类型
所以利用前缀和的思想,如果不计模造成的负数问题$ Hash(l,r)=hash[r]-hash[l-1] \times E^{r-l+1} $
如果考虑模数造成负数问题那么要多mo几次,即 Hash(l,r) = ( (hash[r] - (hash[l-1] * pow[r-l+1] % mo) ) % mo + mo) % mo
得函数Hash(l,r)表示串S的子串[l,r]的哈希值。
那么这样就可以$O(1)$判断两个子串是不是相等了。套个二分就过了。
复杂度$O(m log_2 n)$
# include <bits/stdc++.h> # define int long long # define hash HASH # define pow Pow using namespace std; const int N=1e5+10; const int mo=1e9+7; const int E=51; char s[N]; int n,m,hash[N],pow[N]; inline int read() { int X=0,w=0; char c=0; while(c<'0'||c>'9') {w|=c=='-';c=getchar();} while(c>='0'&&c<='9') X=(X<<3)+(X<<1)+(c^48),c=getchar(); return w?-X:X; } int val(char ch){return ch-'a';} int Hash(int l,int r){ return ((hash[r]-hash[l-1]*pow[r-l+1]%mo)%mo+mo)%mo; } bool check(int len,int l1,int l2){ if ((int) Hash(l1,l1+len-1)==(int) Hash(l2,l2+len-1)) return 1; else return 0; } signed main() { freopen("lcp.in","r",stdin); freopen("lcp.out","w",stdout); n=read();m=read(); scanf("%s",s+1); pow[0]=1; for (int i=1;i<=n;i++) pow[i]=pow[i-1]*E%mo; for (int i=1;i<=n;i++) hash[i]=(hash[i-1]*E+val(s[i]))%mo; int l1,r1,l2,r2; while (m--) { l1=read();r1=read();l2=read();r2=read(); int l=0,r=min(r1-l1+1,r2-l2+1),ans=0; while (l<=r) { int mid=(l+r)>>1; if (check(mid,l1,l2)) ans=mid,l=mid+1; else r=mid-1; } printf("%lld\n",ans); } return 0; }