字符串Hash+二分(poj3974-Palindrome,武辰延的字符串)

在这里插入图片描述

传送门:Palindrome

题意:

给定一个长度为n的字符串,求它的最长回文串

字符串Hash:

  • 直接使用unsigned long long储存Hash值,计算时不处理算术溢出问题(产生溢出时相当于自动对264取模,这样可以避免低效的mod运算)
  • 一般取进制数P为131或13331
  • 在字符串S后加上字符c(其代表数值为 v a l u e [ c ] value\left[ c\right] value[c])后整体Hash值为:
    H ( S + c ) = ( H ( S ) ∗ P + v a l u e [ c ] ) m o d M H\left(S+c\right) =\left( H\left(S\right) \ast P+value\left[ c\right] \right) modM H(S+c)=(H(S)P+value[c])modM
  • 设在字符串S后加上字符串T后Hash值变为 H a s h ( S + T ) Hash(S+T) Hash(S+T),那么有:
    H ( T ) = ( H ( S + T ) − H ( S ) × P l e n g t h ( T ) ) m o d M H\left( T\right) =\left( H\left( S+T\right) -H\left( S\right) \times P^{length\left( T\right)}\right) modM H(T)=(H(S+T)H(S)×Plength(T))modM相当于P进制下在S后面补0的方式,把S左移到与S+T的左端对齐,然后相减即可

二分:

  • 本题分两种情况:最长偶回文子串或最长奇回文子串,要综合两种情况的max
  • 枚举回文子串的中心位置1~len,二分找到每个中心位置对应的回文子串(奇或偶)的max,最后再取一次max,其中二分回文子串长度的复杂度为 log ⁡ N \log N logN,整体复杂度为 N log ⁡ N N\log N NlogN
  • 感觉所有循环从1开始要比0简单一些,相应字符串操作为scanf("%s", s+1); len = strlen(s + 1);

代码:

#include <bits/stdc++.h>
using namespace std;
#define f(i,a,b) for(int i=a;i<b;i++)
#define ff(i,a,b) for(int i=a;i<=b;i++)
#define ull unsigned long long
const int N=1000000+5,P=13331;
char s[N];
//别开int,所有数组都用ull
ull p[N],p1[N],p2[N];
//无符号整数,不会出现负数
ull hash1(int l,int r){
    return (p1[r]-p1[l-1]*p[r-l+1]);
}
//因为倒着存,注意用大的减去小的
ull hash2(int l,int r){
    return (p2[l]-p2[r+1]*p[r-l+1]);
}
int Case;
int main(){
    p[0]=1;
    f(i,1,N)p[i]=p[i-1]*P;
    //字符串从1开始读入
    while(scanf("%s",s+1) && s[1]!='E'){
        Case++;
        int len=strlen(s+1);
        //对s正序求hash
        ff(i,1,len)p1[i]=p1[i-1]*P+s[i];
        //对s逆序求hash
        p2[len+1]=0;//如果上一个字符串比较长,那不重新给p2赋值则会出错
        for(int i=len;i>=1;i--){
            p2[i]=p2[i+1]*P+s[i];
        }
        //res赋值成1或0均可
        int res=1;
        //枚举中点位置i(奇数长度为中点,偶数长度为前一半的右端点)
        //左串或右串长度为0也可以,hash值自然就是0了
        ff(i,1,len){
            //l值设为0,如果设为1的话在牛客只能过掉75%的数据
            int l=0,r=min(i-1,len-i);
            while(l<r){
                //因为hash相等的时候可以取到,所以就用这个二分模板
                int m=(l+r+1)>>1;
                if(hash1(i-m,i-1)==hash2(i+1,i+m))l=m;
                else r=m-1;
            }
            res=max(res,l*2+1);
            //注意区分
            //寻找最长偶回文子串
            l=0,r=min(i-1,len-i+1);
            while(l<r){
                int m=(l+r+1)>>1;
                if(hash1(i-m,i-1)==hash2(i,i+m-1))l=m;
                else r=m-1;
            }
            res=max(res,l*2);
        }
        printf("Case %d: %d\n",Case,res);
    }
}

第二次写的代码(类似的):

#include <cstdio>
#include <cstring>
#include <algorithm>
#define ull unsigned long long
using namespace std;
const int N = 1000006, P = 13331;
char s[N];
ull f1[N], f2[N], p[N];
ull H1(int i, int j) {return (f1[j] - f1[i - 1] * p[j - i + 1]);}
ull H2(int i, int j) {return (f2[i] - f2[j + 1] * p[j - i + 1]);}

int main() {
    int id = 0;
    p[0] = 1; for (int i = 1; i < N; i++) p[i] = p[i - 1] * P;
    while (~scanf("%s", s + 1) && s[1] != 'E') {
        ++id;
        int ans = 0, len = strlen(s + 1);
        f2[len+1] = 0;
        for (int i = 1; i <= len; i++) f1[i] = f1[i - 1] * P + s[i];//不用处理溢出
        for (int i = len; i; i--) f2[i] = f2[i + 1] * P + s[i];
        for (int i = 1; i <= len; i++) {//枚举回文子串的中心位置
            int min_len = 1, max_len = min(i - 1, len - i);//寻找最长奇回文子串,为防止越界,当2*i>=len时取后者
            while (min_len < max_len){
                int mid = (min_len + max_len + 1) >> 1;
                if (H1(i - mid, i - 1) == H2(i + 1, i + mid)) min_len = mid;
                else max_len = mid - 1;
            }
            ans = max(min_len * 2 + 1, ans);

            min_len = 1, max_len = min(i - 1, len - i + 1);
            while (min_len < max_len) {
                int mid = (min_len + max_len + 1) >> 1;
                if (H1(i - mid, i - 1) == H2(i, i + mid - 1)) min_len = mid;
                else max_len = mid - 1;
            }
            ans = max(min_len * 2, ans);
        }
        printf("Case %d: %d\n", id, ans);
    }
    return 0;
}
//abcbabcbabcba中f1[1]=97,f1[2]=1293205

武辰延的字符串:
在这里插入图片描述

思路来源

提示:

这题需要注意一下二分姿势,不然可能过不了

代码:

#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long ll; // ull自动溢出,相当于对2^64取模
const int N=1e5+10,bas=13331; // 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;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;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值