Codeforces 961F: 字符串hash

题意:给一个字符串,求出其border长度(前缀=后缀,且长度最长),然后切掉第一个和最后一个字母,再求border,直到字符串切么得了。

题解:

首先要发现一个性质:设一个字符串的border长度为x,那么,在这个字符串头尾各加上一个字母,新字符串的border长度最多为x+2。

证明的话,考虑反证法:设原串border长度为x,即长度最长的前缀=后缀,假设添加字母之后,border'>x+2,那么把长度=border'的前缀平移到长度=border'的后缀上一比对,容易看出原串border长度>x,产生矛盾。

那么考虑题目所说的这个过程的反过程,就是每次添加字母,每次的添加,最多使得border+2,那么总共最多使得border增加2*n/2=O(n),然后考虑减少的,即,原串border=x,新串border'<x+2,设delta=border-border',发现这个delta总共也是O(n)的(废话,增加的总共O(n),减少的肯定也是O(n)),那么。。直接hash+暴力不就行了吗。。这样总共的check次数是O(2n)的。

注意hash姿势。。出题人精心卡掉了自然溢出的hash姿势。存在另外一种hash姿势:选择一个小质数和一个大质数,小质数作为hash的进制数字,大质数用作取模,并采取多重hash防止冲突。。。总之被卡的已经不省人事了。。主要写博客为了记录下这种hash姿势以及hash质数。。。下次被卡了就拜一下这个题。。


Code:

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e6+100;
const int HASH_NUM = 10;
typedef long long LL;
int AC[HASH_NUM] ={131,137,139,149,151,157,163,167,173,179};
int ACC[HASH_NUM] ={200003,200009,200017,200023,200029,200033,200041,200063,200087,200117};
LL bas[maxn][HASH_NUM];
int n;
char s[maxn];
LL sum1[maxn][HASH_NUM];
int ans[maxn];
bool check(int index,int x,int len){
    LL hs1 = ((sum1[x+len-1][index]-sum1[x-1][index]*bas[len][index]%ACC[index])%ACC[index]+ACC[index])%ACC[index];
    LL hs2 = ((sum1[n-x+1][index]-sum1[n-x-len+1][index]*bas[len][index]%ACC[index])%ACC[index]+ACC[index])%ACC[index];
    if (hs1==hs2)return true;
    else return false;
}
bool check(int x,int len){
    for (int i=0;i<HASH_NUM;i++){
        if (!check(i,x,len))return false;
    }
    return true;
}

int calc(int x){
    for (int len = n-2*(x-1)-1;len;len--){
        if (len%2&&check(x,len)){
            return len;
        }
    }
    return -1;
}
int main(){
    scanf("%d",&n);
    scanf("%s",s+1);
    for (int o =0;o<HASH_NUM;o++){
        bas[0][o]=1;
        for (int i=1;i<=n;i++){
            bas[i][o] = bas[i-1][o]*AC[o]%ACC[o];
        }
        for (int i=1;i<=n;i++){
            sum1[i][o] = (sum1[i-1][o]*AC[o]%ACC[o]+s[i]-'a'+1)%ACC[o];
        }
    }
    int topi = (n+1)>>1;
    for (int i=topi;i>=1;i--){
        if (i==topi){
            if (n%2==1){
                ans[i]=-1;
            }else{
                if (s[i]==s[i+1]){
                    ans[i]=1;
                }else{
                    ans[i]=-1;
                }
            }
        }else{
            ans[i] = ans[i+1]+2;
            while (ans[i]!=-1&&check(i,ans[i])==false){
                ans[i]-=2;
            }
        }
    }
    for (int i=1;i<=topi;i++){
        printf("%d ",ans[i]);
    }
    return 0;
}

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值