CHAPTER_12 提高篇(6)——字符串专题
12.1 字符串hash进阶
(续上节)
我们现在重新用字符串hash+二分的方法来解决11.5节中的最长回文子串的问题。
题目:
给出一个字符串S,字母区分大小写,求S的最大回文子串的长度
输入样例:
PATZJUJZTACCBCC
输出样例:
9
//最长回文子串为ATZJUJZTA,长度为9
思路:
对于一个给定的字符串str,可以先求出其字符串hash数组H1,然后再将str反转,求出反转字符串str的hash数组H2。接着分回文串的奇偶情况进行讨论。
(1)回文串的长度是奇数:枚举回文中心点 i ,二分子串的半径k,找到最大的使子串[i-k,i+k]是回文串的k。其中判断子串[i-k,i+k]是回文串等价于判断str的两个子串[i-k,i]和[i,i+k]是否是相反的串。而这等价于判断str的[i-k,i]子串与反转字符串rstr的[len-1-(i+k),len-1-i]子串是否相同( [a,b]在反转字符串中的位置为[len-1-(i+k),len-1-i] ),因此只需要判断H1[i-k...i]与H2[len-1-(i+k)...len-1-i]是否相等即可。
(2)回文串的长度是偶数:枚举回文空隙点,令 i 表示空隙左边第一个元素的下标,二分子串的板甲k,找到最大的使子串[i-k+1,i+k]是回文串的k。其中判断子串[i-k+1,i+k]是回文串等价于判断str的两个子串[i-k+1,i]和[i+1,i+k]是否是相反的串。而这等价于判断str的[i-k+1,i]子串与反转字符串rstr的[len-1-(i+k),len-1-(i+1)]子串是否相同,因此只需要判断H1[i-k+1...i]与H2[len-1-(i+k)...len-1-(i+1)]是否相等即可。
参考代码:
#include<iostream>
#include<string>
#include<vector>
#include<algorithm>
using namespace std;
typedef long long LL;
const LL mod=1000000007;
const LL p=10000019;
const int maxn=10001;
LL powP[maxn]; //powP[i]存放p^i%mod
LL H1[maxn]={0},H2[maxn]={0}; //存放str1,str2的hash值
//初始化powP函数
void init(int len) {
powP[0]=1;
for(int i=1;i<=len;i++) {
powP[i]=(powP[i-1]*p)%mod;
}
}
//计算字符串str的hash值
void calH(LL H[],string &str) {
H[0]=str[0]-'a'; //H[0]单独处理
for(int i=1;i<str.size();i++) {
H[i]=(H[i-1]*p+str[i]-'a')%mod;
}
}
//计算单个H[i...j]
int calSingleSubH(LL H[],int i,int j) {
if(i==0)
return H[j];
else {
return ((H[j]-H[i-1]*powP[j-i+1])%mod+mod)%mod;
}
}
//对称点为i,字符串长len,在[l,r]里二分回文半径
//寻找最后一个满足条件hashL==hashR的回文半径
//等价于寻找一个满足条件hashL!=hashR的回文半径,然后减一
//isEven当求奇回文时为0,偶回文时为1
int binarySearch(int l,int r,int len,int i,int isEven) {
while(l<r) {
int mid=(l+r)/2;
//左半子串hash值H1[H1L...H1R],右半子串hash值H2[H2L...H2R]
int H1L=i-mid+isEven,H1R=i;
int H2L=len-1-(i+mid),H2R=len-1-(i+isEven);
int hashL=calSingleSubH(H1,H1L,H1R);
int hashR=calSingleSubH(H2,H2L,H2R);
if(hashL!=hashR)
r=mid; //hash值不等,说明回文半径<=mid
else
l=mid+1; //hash值相等,说明回文半径>mid
}
return l-1; //返回最大回文半径
}
int main() {
string str;
cin>>str;
init(str.size());
calH(H1,str); //计算str的hash值
reverse(str.begin(),str.end()); //反转字符串
calH(H2,str); //计算rstr的hash值
int ans=0;
//偶回文
for(int i=0;i<str.length();i++) {
//二分上界为分界点i的左右长度的较小值加一
int maxLen=min(i+1,(int)str.size()-1-i)+1;
int k=binarySearch(0,maxLen,str.size(),i,1);
ans=max(ans,k*2);
}
for(int i=0;i<str.length();i++) {
//二分上界为分界点i的左右长度的较小值加一
int maxLen=min(i,(int)str.size()-1-i)+1;
int k=binarySearch(0,maxLen,str.size(),i,0);
ans=max(ans,k*2+1);
}
cout<<ans<<endl;
return 0;
}