二分+哈希
链接:https://ac.nowcoder.com/acm/contest/9984/B
来源:牛客网
输入描述:
第一行一个字符串 s 。
第二行一个字符串 t 。
其中 1≤∣s∣,∣t∣≤1e5 ,只包含小写字母。
输出描述:
输出一行一个整数,表示满足条件的前缀的对数。
说明
s1+s1=t2
s1+s2=t3
s2+s1=t3
题意是找出字符串t的前缀,使之由字符串s的两个前缀组成,即t[i+j]==s[i]+s[j],求满足条件的总对数。
首先根据前缀的特性不难想到,必须要有ti=si,将上面的式子变一下形就可以看出来了,s[i+j]-s[i]==s[j]
就是s[i+j]-s[i]之后从i+1之后的字符串和j的前缀能匹配的最大长度就是就是i的贡献
具体算法就是哈希和二分:
哈希能够O(1)判断两个子串是否匹配(字符串哈希算法)
二分是因为我们在找到某个前缀匹配时,前缀的前缀也一定匹配,
所以可以二分找到最长前缀,因为比它长的前缀都不匹配,比它短的所有前缀都匹配,这就满足二分使用前提的单调性。
举个例子:
aab
aaab
枚举i=1,有相同前缀为'a',为'aab',最长能匹配上的前缀为'aab',其长度为3,答案+3。
枚举i=2,有相同前缀为'aa',为'ab',最长能匹配上的前缀为'a',其长度为1,答案+1。
枚举i=3,没有相同前缀('aab' != 'aaa'),结束,答案为4。
就是这样
#include<iostream> #include<algorithm> #include<queue> #include<cstring> using namespace std; typedef unsigned long long ll; const int maxn=3e5+100,base=131; ll sum1[maxn],sum2[maxn],p[maxn]; char s1[maxn],s2[maxn]; int n1,n2; ll has1(int l,int r){//0(1)获得s[l,r]的哈希值 return sum1[r]-sum1[l-1]*p[r-l+1]; } ll has2(int l,int r){ if(r>n2) return 0; return sum2[r]-sum2[l-1]*p[r-l+1]; } int main(){ scanf("%s%s",s1+1,s2+1); n1=strlen(s1+1); n2=strlen(s2+1); sum1[0]=sum2[0]=0; p[0]=1; for(int i=1;i<=max(n1,n2);i++){ if(i<=n1) sum1[i]=sum1[i-1]*base+s1[i]; if(i<=n2) sum2[i]=sum2[i-1]*base+s2[i]; p[i]=p[i-1]*base; } ll ans=0; for(int i=1;i<=n2;i++){ if(s1[i]==s2[i]){ int l=1,r=n1; int temp=0; while(r>=l){ int mid=(l+r)/2; if(has1(1,mid)==has2(i+1,i+mid)){ l=mid+1; temp=mid; } else{ r=mid-1; } } ans+=temp; } else{ break; } } cout<<ans<<endl; }
题意是找出字符串t的前缀,使之由字符串s的两个前缀组成,即ti+j=si+sj,求满足条件的总对数。
首先根据前缀的特性不难想到,必须要有ti=si,这是必要条件。那么枚举s和t的所有相同前缀si,然后求t−si,找其能匹配上s的前缀sj的最大长度,计入答案即可。
具体算法就是哈希和二分:
-
哈希能够O(1)判断两个子串是否匹配(字符串哈希算法)
-
二分是因为我们在找到某个前缀匹配时,前缀的前缀也一定匹配,所以可以二分找到最长前缀,因为比它长的前缀都不匹配,比它短的所有前缀都匹配,这就满足二分使用前提的单调性。
举个例子:
s串:aab
t串:aaab
枚举i=1,有相同前缀si为’a’,t−si为’aab’,最长能匹配上s的前缀为’aab’,其长度为3,答案+3。
枚举i=2,有相同前缀si为’aa’,t−si为’ab’,最长能匹配上s的前缀为’a’,其长度为1,答案+1。
枚举i=3,没有相同前缀(‘aab’ != ‘aaa’),结束,答案为4。