2021牛客寒假算法基础集训营4 B题 武辰延的字符串【字符串哈希+二分】
武辰延的字符串
https://ac.nowcoder.com/acm/contest/9984/B
本文同步更新于我的CSDN博客:https://blog.csdn.net/ljw_study_in_CSDN/article/details/113868472
思路
题意是找出字符串的前缀,使之由字符串的两个前缀组成,即,求满足条件的总对数。
首先根据前缀的特性不难想到,必须要有,这是必要条件。那么枚举和的所有相同前缀,然后求,找其能匹配上的前缀的最大长度,计入答案即可。
具体算法就是哈希和二分:
哈希能够O(1)判断两个子串是否匹配(字符串哈希算法)
二分是因为我们在找到某个前缀匹配时,前缀的前缀也一定匹配,所以可以二分找到最长前缀,因为比它长的前缀都不匹配,比它短的所有前缀都匹配,这就满足二分使用前提的单调性。
举个例子:
aab
aaab
枚举i=1,有相同前缀为'a',为'aab',最长能匹配上的前缀为'aab',其长度为3,答案+3。
枚举i=2,有相同前缀为'aa',为'ab',最长能匹配上的前缀为'a',其长度为1,答案+1。
枚举i=3,没有相同前缀('aab' != 'aaa'),结束,答案为4。
AC代码
#include <bits/stdc++.h> using namespace std; typedef unsigned long long ll; // ull自动溢出,相当于对2^64取模 const int N=1e5+10,bas=131; // bas自定,只要能尽量避免哈希冲突 int n1,n2; ll sum1[N],sum2[N],p[N]; // sum1,sum2存哈希的前缀和,p[i]=bas^i char s1[N],s2[N]; ll has1(int l,int r) // O(1)得到[l,r]区间内sum1的哈希值 { 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() { ios::sync_with_stdio(false); cin>>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]*bas+s1[i]; if(i<=n2)sum2[i]=sum2[i-1]*bas+s2[i]; p[i]=p[i-1]*bas; // p[i]=bas^i } ll ans=0; for(int i=1;i<=n2-1;i++) // 第2个字符串减去相同前缀[1,i] { if(s1[i]!=s2[i])break; if(s2[i+1]!=s1[1])continue; // 剩下的第2个字符串连第1个字符都匹配不上 int l=1,r=n1,tmp=0; while(l<=r) { int mid=(l+r)/2; // 假设长度为mid ll t1=has1(1,mid); // 第1个字符串前缀哈希值 ll t2=has2(i+1,i+mid); // 第2个字符串剩余前缀哈希值 if(t1==t2)tmp=mid,l=mid+1; // 匹配上了,长度继续变大 else r=mid-1; } ans+=tmp; } printf("%llu\n",ans); return 0; } /* aaaaaabbb aaaaaaaaaaa ans:35 aaaaa aaaa ans:6 aaaa aaaaa ans:10 abcdabcd abcd ans:0 abcd abcdabcd ans:4 abc aab ans:2 */
后记
当时没好好学哈希,以为unordered_map就能代替相同功能,然而碰到这题就束手无策了,问了yz大佬才搞懂思路,我自己写还wa了5发,真的菜哭了QAQ