PAT 甲级 1040 Longest Symmetric String DP和字符Hash

本文介绍了使用动态规划和字符哈希解决PAT甲级1040题目的方法。通过计算字符串子串的哈希值,利用公式进行翻转和比较,判断回文子串。针对回文半径的二分查找,确定问题是upper_bound类型,确保搜索范围内一定能找到目标值。
摘要由CSDN通过智能技术生成

动态规划

#include <bits/stdc++.h>
using namespace std;
int dp[1010][1010]; 
int main() {
    string s;
    getline(cin,s);
    size_t ans=0;
    for(size_t len=0;len<s.size();++len){
        for(size_t i=0,j=0;i+len<s.size();++i){
            j=i+len;
            if(len==0) dp[i][j]=1;
            else if(len==1) dp[i][j]=(s[i]==s[j]);
            else dp[i][j]=(s[i]==s[j]&&dp[i+1][j-1]);
            ans=(dp[i][j]&&j-i>ans?j-i:ans);
        }
    }
    cout<<ans+1;
}

字符哈希

字符串 s [ 0 , i ] [0,i] [0,i] 子串哈希的值满足公式:h[i]=(h[i-1]*P+s[i])%MOD,其中,P=1e7+19MOD=1e9+7,是两个大质数。 [ i , j ] [i,j] [i,j] 子串的哈希值满足公式hval(h,i,j)=((h[j]-h[i]*pow_p[j-i+1])%MOD+MOD)%MOD,其中 pow_p[i] 表示 P i P^i Pi。欲证明,展开 h[j] 即可。

于是可以获得 s 对应的 h1[s],然后把 s 翻转获得 h2[s]。对奇子串 [ i − k , i + k ] [i-k,i+k] [ik,i+k](回文半径为 k k k,回文中心为 i i i),判断是否为回文串,相当于判断 s [ i − k , i ] [i-k,i] [ik,i] [ i , i + k ] [i,i+k] [i,i+k] 字串是否相等,即 hval(h1,i-k,i)==hval(h2,len-1-(i-k),len-1-i);对偶子串 [ i − k + 1 , i + k ] [i-k+1,i+k] [ik+1,i+k](回文半径为 k k k,回文中心为 i i i 右边的空隙),相当于判断 s 的子串 [ i − k + 1 , i ] [i-k+1,i] [ik+1,i] [ i + 1 , i + k ] [i+1,i+k] [i+1,i+k] 是否相等,即 hval(h1,i-k,i)==hval(h2,len-1-(i+k),len-1-(i+1))。于是可以枚举回文中心,对回文半径二分。

接下来一个问题就是,这里的二分是 lower_bound 问题还是 upper_bound 问题?对比两种问题:

  • lower_bound: [ m i d ] < t a r g e t [mid]<target [mid]<target 时, l ← m i d + 1 l \leftarrow mid+1 lmid+1 [ m i d ] ≥ t a r g e t [mid] \ge target [mid]target 时, r ← m i d r \leftarrow mid rmid
  • upper_bound: [ m i d ] ≤ t a r g e t [mid] \le target [mid]target 时, l ← m i d + 1 l \leftarrow mid+1 lmid+1 [ m i d ] > t a r g e t [mid] > target [mid]>target 时, r ← m i d r \leftarrow mid rmid

(详细可以见这篇文章。)

上面判断返回 false 时,说明回文半径 mid 大于目标回文半径,需要改变右边界。如果是 lower_bound 问题,此时搜索的是第一个大于等于目标回文半径的值, r=mid 不能严格缩小搜索范围,r=mid-1 则可能排除掉目标回文半径(当搜索域不存在目标回文半径时),于是,说明这个问题是upper_bound问题,搜索的是第一个大于目标回文半径的值,即搜索完成后 l=k+1

但是这道题,搜索域中一定能找到目标值(奇数情况为1,偶数情况为0)上述讨论中用 lower_boud 思想看似也可以,此时判断失败时 r=mid-1,判断成功时 l=mid,相应的返回值也要改变。但是,由于 mid=(l+r)/2 的向左归一特性,最后会导致死循环,所以这条路走不通。

#include<bits/stdc++.h>
using namespace std;
const int MAXN=1010;
using LL=long long;
const LL P=1e7+19,MOD=1e9+7;
LL pow_p[MAXN],h1[MAXN],h2[MAXN];
string s;
int GetSubStrH(LL h[],int i,int j){
    if(i==0) return h[j];
    return ((h[j]-h[i-1]*pow_p[j-i+1])%MOD+MOD)%MOD;
}
int BS(int i,bool is_even){
    int sz=s.size();
    // 二分查找第一个大于回文半径的 mid,upper_bound 问题。 
    if(!is_even){
        int l=0,r=min(i,sz-1-i)+1;
        while(l<r){
            int mid=(l+r)/2;
            int hval1=GetSubStrH(h1,i-mid,i);
            int hval2=GetSubStrH(h2,sz-1-(i+mid),sz-1-i);
            if(hval1!=hval2) r=mid;
            else l=mid+1;
        }
        return 2*(l-1)+1;
    }else{
        int l=1,r=min(i+1,sz-i)+1;
        while(l<r){
            int mid=(l+r)/2;
            int hval1=GetSubStrH(h1,i-mid+1,i);
            int hval2=GetSubStrH(h2,sz-1-(i+mid),sz-1-(i+1));
            if(hval1!=hval2) r=mid;
            else l=mid+1;
        }
        return 2*(l-1);
    }
}
int main(){
    pow_p[0]=1;
    for(int i=1;i<MAXN;++i) pow_p[i]=(pow_p[i-1]*P)%MOD;
    
    getline(cin,s);
    int sz=s.size();
    
    h1[0]=s[0];
    for(int i=1;i<sz;++i) h1[i]=(h1[i-1]*P+s[i])%MOD;
    reverse(s.begin(),s.end());
    h2[0]=s[0];
    for(int i=1;i<sz;++i) h2[i]=(h2[i-1]*P+s[i])%MOD;
    
    int ans=0;
    for(int i=0;i<sz;++i){
        int k=max(BS(i,false),BS(i,true));
        ans=max(ans,k);
    }
    cout<<ans;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值