[GDOI模拟2015.08.18]解密

题目大意

给定一篇由若干个单词构成的原文,还有一个由若干个单词组成的句子。加密文是由原文单词通过某个单词(可能一样)替换而成的,原文相同单词一定会被相同加密文单词替换。没有两个不同的原文单词被同一个加密文单词替换。
要求找出句子在加密文中第一次出现的位置。
原文字符总和不超过 1000000 ,句子字符总和不超过 1000000 。所有单词由若干小写字母组成。

题目分析

这题一看就大概知道是字符串的模式匹配问题。正解是用最小表示法表示字符串,然后上KMP或是HASH。
我比赛时想了一个比较另类的方法。我们将原文第 i 个单词通过一个方法表示: succ0,i 表示第 i 个原文中的单词距离下一个一样的单词(后继)的距离,如果后面没有就为 0 。句子也用相同方法表示(句子中后继的距离)为 succ1,i 。这种表示法能排除单词替换的影响,表示出一连串单词的性质了。
这个时候能否直接上KMP算法呢?我们可以发现一个很显然的反例。记原文为 article ,句子为 sentence 。假设我们句子要匹配文章第 i 个单词开始的一连串单词,中间有一个 j(ij<i+length(sentence)) ,满足 succ0,j+ji+length(sentence) 。这时一般匹配会判断两串第 ji+1 位不等,但是实际上, articlej 的后继已经超过了比较范围,对答案没有影响。所以,匹配的开始位置会影响每个单词的表示。也就是当原文第 i 个单词在匹配范围内有后继时,它表示为 succ0,i ,否则表示为 0
这种能模式串随匹配串位置改变的KMP我没有YY出来,于是我打了个HASH。我们发现,如果我们顺序枚举匹配位置,每个原文单词值最多会变化两次(从 0 变为 succ0,i )。所以我们可以将原文每个 i 用模拟链表之类的东西挂在 i+succ0,i 的位置上。预处理句子的哈希值,然后从左到右枚举匹配位置,同时处理当前哈希值,单词值变化的处理,只需对于匹配位置最右端挂着的位置,将哈希数中相应位置加上相应的哈希值即可。
感觉讲得很乱,不懂的就看看代码实现吧。处理后继 succ 我打了个 Trie ,空间卡得很艰难(题目给的空间也太™小了)。

代码实现

#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <ctime>

using namespace std;

typedef long long LL;

const int N=1000000;

int seed[4][3]={{314351,8761,155921},{180503,6899,935381},{78101,59659,414977},{9999889,1000000007,9876809}};
char article[N+1],sentence[N+1];
int aword[N+2],sword[N+2];
int power[3][N+1];
int succ[2][N+1];
char temp[N+1];
int sthash[3];
int prime[4];
int n,m,ans,as,ss;

struct TRIE
{
    int tov[N+1],next[N+1],ch[N+1];
    int last[N+1],key[N+1];
    int etot,ptot,root;

    void init()
    {
        memset(next,0,sizeof next);
        memset(last,0,sizeof last);
        memset(key,0,sizeof key);
        etot=ptot=0;
    }

    void insert(int len,int pos,int kind)
    {
        int rt=root?root:root=++ptot;
        bool found;
        int i,y;
        for (int l=0;l<len;l++)
        {
            found=false;
            i=last[rt];
            while (i)
            {
                y=tov[i];
                if (ch[i]==temp[l]-'a'+1)
                {
                    found=true;
                    break;
                }
                i=next[i];
            }
            if (found)
                rt=y;
            else
            {
                key[++ptot]=0;
                tov[++etot]=ptot;
                ch[etot]=temp[l]-'a'+1;
                next[etot]=last[rt];
                last[rt]=etot;
                rt=ptot;
            }
        }
        if (key[rt])
            succ[kind][key[rt]]=pos-key[rt];
        key[rt]=pos;
    }
}trie;

void hang(int x,int y)
{
    trie.tov[++trie.etot]=y;
    trie.next[trie.etot]=trie.last[x];
    trie.last[x]=trie.etot;
}

void read()
{
    n=m=0;
    char ch=getchar();
    int la=0;
    while (ch!='$')
    {
        while ((ch<'a'||ch>'z')&&ch!='$')
        {
            if (ch==' ')
                aword[++as]=la;
            ch=getchar();
        }
        if (ch>='a'&&ch<='z')
            la=n;
        while (ch>='a'&&ch<='z')
        {
            article[n++]=ch;
            ch=getchar();
        }
    }
    aword[as+1]=n;
    article[n]='\0';
    ch=getchar();
    la=0;
    while (ch!='$')
    {
        while ((ch<'a'||ch>'z')&&ch!='$')
        {
            if (ch==' ')
                sword[++ss]=la;
            ch=getchar();
        }
        if (ch>='a'&&ch<='z')
            la=m;
        while (ch>='a'&&ch<='z')
        {
            sentence[m++]=ch;
            ch=getchar();
        }
    }
    sword[ss+1]=m;
    sentence[m]='\0';
}

void preparation()
{
    int len;
    for (int i=1;i<=as;i++)
    {
        len=0;
        for (int j=aword[i];j<aword[i+1];j++)
            temp[len++]=article[j];
        temp[len]='\0';
        trie.insert(len,i,0);
    }
    trie.init();
    for (int i=1;i<=ss;i++)
    {
        len=0;
        for (int j=sword[i];j<sword[i+1];j++)
            temp[len++]=sentence[j];
        temp[len]='\0';
        trie.insert(len,i,1);
    }
    trie.init();
    for (int i=1;i<=as;i++)
        if (succ[0][i])
            hang(i+succ[0][i],i);
    srand(time(0));
    for (int i=0;i<4;i++)
        prime[i]=seed[i][rand()%3];
    for (int i=0;i<3;i++)
    {
        power[i][0]=1;
        for (int j=1;j<=n;j++)
            power[i][j]=(LL)power[i][j-1]*prime[i]%prime[3];
        sthash[i]=0;
        for (int j=1;j<=ss;j++)
            sthash[i]=(((LL)sthash[i]*prime[i])%prime[3]+succ[1][j])%prime[3];
    }
}

void solve()
{
    int hash[3]={0},item,ptr;
    for (int i=1;i<=ss-1;i++)
        for (int j=0;j<3;j++)
        {
            hash[j]=(LL)hash[j]*prime[j]%prime[3];
            hash[j]=((LL)hash[j]+(succ[0][i]+i>i?0:succ[0][i]))%prime[3];
            ptr=trie.last[i];
            while (ptr)
            {
                item=trie.tov[ptr];
                hash[j]=((LL)hash[j]+((LL)power[j][i-item]*succ[0][item])%prime[3])%prime[3];
                ptr=trie.next[ptr];
            }
        }
    int la=1,tmp;
    for (int i=ss;i<=as;i++)
    {
        for (int j=0;j<3;j++)
        {
            hash[j]=(LL)hash[j]*prime[j]%prime[3];
            hash[j]=((LL)hash[j]+(succ[0][i]+i>i?0:succ[0][i]))%prime[3];
            ptr=trie.last[i];
            while (ptr)
            {
                item=trie.tov[ptr];
                if (item>=i-ss+1)
                    hash[j]=((LL)hash[j]+((LL)power[j][i-item]*succ[0][item])%prime[3])%prime[3];
                ptr=trie.next[ptr];
            }
        }
        if (hash[0]==sthash[0]&&hash[1]==sthash[1]&&hash[2]==sthash[2])
        {
            ans=i-ss+1;
            break;
        }
        tmp=succ[0][i-ss+1]+i-ss+1>i?0:succ[0][i-ss+1];
        for (int j=0;j<3;j++)
            hash[j]=(((LL)hash[j]-((LL)tmp*power[j][ss-1]%prime[3]))%prime[3]+prime[3])%prime[3];
    }
}

int main()
{
    freopen("decryption.in","r",stdin);
    freopen("decryption.out","w",stdout);
    read();
    preparation();
    solve();
    printf("%d\n",ans);
    fclose(stdin);
    fclose(stdout);
    return 0;
}

无谓扯淡

看到这题,其实大家都想到什么。当然,就是OJ的代码相似度判断。我脑补了一下,OJ其实可以将代码缩进回车空格删除,拆成若干表达式和句子,然后用这题算法的改进版来判断相似度(大神勿喷)。

你可能感兴趣的:(hash,trie,OI)