GDOI2016模拟8.18解密

题目
Mirko要解一段加密文,但他只知道某一个句子是原文的一部分。你的任务是要在密文中找到第一个对应这个句子的地方。

文段是通过用某个单词(可能和原文一样的单词)替换原始文段每一个单词来加密的。如果某些单词在原文出现一次以上,就会使用相同的替换单词来替换。没有两个不同的单词使用相同的替换单词。

单词是通过空格隔开的小写字母序列。句子是连续单词的序列。

这题我们可以通过最小表示法+hash来做。

首先对于模板串,我们记录下数组A[i]表示成与第i个字符串相同的下一个串在哪个位置,然后将其视作一个P进制数(P是一个质数),然后mod P1(P1是另一个大质数)将其hash起来,得到新的表示形式S(这个可以证明和将相同的字符串表示成相同的编号并使字典序最小的最小表示法是等价的,因为我们并不关心具体的字符串,只关心相同字符串之间的关系(一个比较寻常的联系就是它们之间的距离和出现的位置,将位置视作数位,将距离视作该数位的数值,得到一个k进制数即可表示(当然,比较多变化的通常是数值:有时候是距离,有时候是编号)))

用相同的方法我们可以处理匹配串的前len个字符串(len为匹配串的字符串个数),注意,若相同的字符串超出len(即i和j为最近的相同字符串,i在len以内,j在len以外,则将i那一位视作0)则不计算那一位(把距离看做0),由于多出来的在模板串是没出现的,得到匹配串的新型式F,将F与S比较,如相同则为ans。

接着我们可以试着移动指针来匹配匹配串中后续len个字符,这里我们可以将F看做P1进制数,当前面出去一个,(若之前加入过,有的距离会超过len,所以会有没有加入过的)将其减去,若出现l≤i≤r,且最近的与i相同字符串位于r(也就是之前并没计入,后来要计入了),就找到i对应的数位加上就行。

每次处理完就与S比较一下,ans就是第一次S与F相同的位置

对于找相邻相同的字符串,扫一遍,可以用双hash(数据有卡),也可以转成26进制(不能直接用string否则爆空间)后取mod用map来弄。

贴代码(由于担心出错几率大,我将距离乘上一个权值再转成k进制,这里可以不乘,若担心因此看不懂程,可以将help[]和MOD1去掉)

#include<iostream>
#include<algorithm>
#include<cstdio>
#define MOD 1000000007
#define MOD3 10007
#define MOD1 9999991
#define MOD2 1000007
#include<map>
#include<cstring>
using namespace std;
#define N 1000001
map<long long,int>f;
int n,len,len1,ans;
long long s;
long long a[N],b[N],help[N],help1[N];
char c[N];
long long get(){
    static int len;
    static long long s,s1;
    len=strlen(c);
    s=0;
    for (int i=0;i<len;i++)
        s=(s*26+c[i]-'a')%MOD;
    s1=0;
    for (int i=0;i<len;i++)
        s1=(s1*26+c[i]-'a')%MOD3;
    return s+s1*MOD;
}
void pre(){
    help[0]=help1[0]=1;
    for (int i=1;i<N;i++)
        help[i]=help[i-1]*MOD1%MOD,help1[i]=help1[i-1]*MOD2%MOD;
}
void init(){
    static long long x,y,z;
    y=0;
    scanf(" %s",&c);
    while (c[0]!='$'){
        x=get();
        a[++y]=f[x];
        b[a[y]]=y;
        f[x]=y;
        scanf(" %s",&c);
    }
    len1=y;
    y=0;
    scanf(" %s",&c);
    f.clear();
    while (c[0]!='$'){
        x=get();
        z=f[x];
        ++y;
        s=(s*MOD2)%MOD;
        if (z)
            s=(s+help[y-z])%MOD;
        f[x]=y;
        scanf(" %s",&c);
    }
    len=y;
    f.clear();
}
void work(){
    static long long ss,l;
    ss=0;
    for (int i=1;i<=len;i++){
        ss=(ss*MOD2)%MOD;
        if (a[i])
            ss=(ss+help[i-a[i]])%MOD;
    }
    if (ss==s){
        ans=1;
        return;
    }
    l=0;
    for (int i=len+1;i<=len1;i++){
        l++;
        if (b[l]&&b[l]<i)
            ss=((ss-help[b[l]-l]*help1[i-b[l]-1])%MOD+MOD)%MOD;
        ss=(ss*MOD2)%MOD;
        if (a[i]&&a[i]>l)
            ss=(ss+help[i-a[i]])%MOD;
        if (ss==s){
            ans=i-len+1;
            return;
        }
    }
}
void write(){
    printf("%d",ans);
}
int main(){
    pre();
    init();
    work();
    write();
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值