[GDOI模拟2015.08.18]解密

来源:互联网 发布:matlab编程题 编辑:程序博客网 时间:2024/06/05 16:35

题目大意

给定一篇由若干个单词构成的原文,还有一个由若干个单词组成的句子。加密文是由原文单词通过某个单词(可能一样)替换而成的,原文相同单词一定会被相同加密文单词替换。没有两个不同的原文单词被同一个加密文单词替换。
要求找出句子在加密文中第一次出现的位置。
原文字符总和不超过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其实可以将代码缩进回车空格删除,拆成若干表达式和句子,然后用这题算法的改进版来判断相似度(大神勿喷)。

0 0
原创粉丝点击