ZOJ-4030 JUMPin' JUMP UP!!!(浙江省赛 G 题 字符串hash)

题目链接:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=5771

题目大意:题目定义对任意两个串X , Y,如果存在一个字符串Z满足XZ=ZY,那么这两个串就是完美匹配的。

现在给你一个长串A(长度为n),短串B(长度为m),

总共q次查询,每次查询给你一个起点x,和长度y

要你求出有多少个字符串Z可以使 在长串A中以x为起点长度为m的子串a 和 短串B 满足BZ=Za。(要求加上字符串Z之后串的长度不能超过y)。

题目思路:对于任意的两个串X和Y,如果要满足完美匹配,一定会满足如下的条件

在求出断点之后,我们只需要加上X串的最小循环节的长度倍数,得到的仍旧是解。

那么我们就可以先预处理出短串B所有的断点,并将断点前的串补到串的最后的情况,用hash值存储,在每次查询时,只需要判断短串所有断点的情况中是否有与长串A的子串相同的串就行了,如果有,就利用最小循环节找出所有小于长度y的情况就是答案了。

这里有一个处理出短串B所有的断点移动情况的方法:

我们知道一个字符串前 i 位的hash值为 H[i]=H[i-1]*hash+s[i],我们令P[i]=P[i-1]*hash,设字符串的长度为len,那么整个串的hash值为H[len];

如果我们把当前字符串的第一个字符移动到最后一个字符的后面,那么当前串的hash值就会变为

H=H[len]*has+s[1]-s[1]*P[len] (这是根据hash值转移的式子分解出来的,这里就不详解了)

那么我们就可以通过这个式子,将第二个字符,第三个字符...直到最后一个字符移动到串的最后面的所有情况的hash值都求出来了,时间复杂度为O(len)

具体实现可以看代码:

#include <bits/stdc++.h>
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define fi first
#define se second
#define pb push_back
#define MP make_pair
#define FIN freopen("in.tx","r",stdin)
#define FOUT freopen("out.txt","w",stdout)
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int,int>pii;
typedef pair<LL,LL>pll;
const int inf = 0x3f3f3f3f;
const int mod1 = 1e9+7;
const int mod2 = 1e9+9;
const int MX = 1e5+7;
const int has = 99959;

int n,m,q;
int nxt[MX];
int a[MX];
char str1[MX],str2[MX];
LL P1[MX],P2[MX];
pll H[MX];//本题的数据比较厉害,用普通的hash会被卡,得用双重hash来存储才能过。
unordered_map<LL,int>mp;

void Hash(){
    //预处理出str1的hash值,方便求解子串的hash值
    P1[0]=P2[0]=1;
    for(int i=1;i<=n;i++){
        P1[i]=(P1[i-1]*has)%mod1;
        P2[i]=(P2[i-1]*has)%mod2;
        LL h1=((H[i-1].fi*has)%mod1+(LL)str1[i])%mod1;
        LL h2=((H[i-1].se*has)%mod2+(LL)str1[i])%mod2;
        H[i]=MP(h1,h2);
    }
}

LL get_hash(int l,int r){
    //得到子串str1[l,r]的hash值
    LL h1=(H[r].fi-(H[l-1].fi*P1[r-l+1])%mod1+mod1)%mod1;
    LL h2=(H[r].se-(H[l-1].se*P2[r-l+1])%mod2+mod2)%mod2;
    return h1+h2;
}


int main(){
    int T;cin>>T;
    while(T--){
        scanf("%d%d%d",&n,&m,&q);
        scanf("%s%s",str1+1,str2+1);
        mp.clear();
        Hash();
        nxt[0]=-1;
        for(int i=1;i<=m;i++){
            int p=nxt[i-1];
            while(p>=0 && str2[p+1]!=str2[i]) p=nxt[p];
            nxt[i]=p+1;
        }
        int len=m-nxt[m];
        if(nxt[m]*2<m) len=m;
        //求出短串的最小循环节len
        pll h=MP(0,0);
        for(int i=1;i<=m;i++){
            LL h1=((h.fi*has)%mod1+(LL)str2[i])%mod1;
            LL h2=((h.se*has)%mod2+(LL)str2[i])%mod2;
            h=MP(h1,h2);
        }
        int cnt=0;
        for(int i=1;i<=m;i++){
            //这里处理出短串str2所有断点移动后的串的hash值
            LL h1=((h.fi*has)%mod1+(LL)str2[i])%mod1;
            LL h2=((h.se*has)%mod2+(LL)str2[i])%mod2;
            h=MP((h1-(LL)str2[i]*P1[m]%mod1+mod1)%mod1,(h2-(LL)str2[i]*P2[m]%mod2+mod2)%mod2);
            if(!mp[h.fi+h.se]){
                mp[h.fi+h.se]=++cnt;
                a[cnt]=i;
                //a记录每个断点的长度;
                //注意:可能不同的断点会形成相同的串,相同的串肯定是取越短的长度解才更优
            }
        }
        while(q--){
            int j;LL k;
            scanf("%d%lld",&j,&k);
            LL hh=get_hash(j,j+m-1);//得到子串的hash值
            int p=mp[hh];
            if(!p)
                printf("0\n");
            else
                printf("%lld\n",(LL)(k-a[p]+len)/len);
                //所有可行的串都满足a[p]+q*len<=k,这时找到最大的q就是答案了
        }
    }
    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值