[GDOI模拟2015.08.18]解密
2015-08-23 20:40
381 查看
题目大意
给定一篇由若干个单词构成的原文,还有一个由若干个单词组成的句子。加密文是由原文单词通过某个单词(可能一样)替换而成的,原文相同单词一定会被相同加密文单词替换。没有两个不同的原文单词被同一个加密文单词替换。要求找出句子在加密文中第一次出现的位置。
原文字符总和不超过1000000,句子字符总和不超过1000000。所有单词由若干小写字母组成。
题目分析
这题一看就大概知道是字符串的模式匹配问题。正解是用最小表示法表示字符串,然后上KMP或是HASH。我比赛时想了一个比较另类的方法。我们将原文第i个单词通过一个方法表示:succ0,i表示第i个原文中的单词距离下一个一样的单词(后继)的距离,如果后面没有就为0。句子也用相同方法表示(句子中后继的距离)为succ1,i。这种表示法能排除单词替换的影响,表示出一连串单词的性质了。
这个时候能否直接上KMP算法呢?我们可以发现一个很显然的反例。记原文为article,句子为sentence。假设我们句子要匹配文章第i个单词开始的一连串单词,中间有一个j(i≤j<i+length(sentence)),满足succ0,j+j≥i+length(sentence)。这时一般匹配会判断两串第j−i+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 ='\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其实可以将代码缩进回车空格删除,拆成若干表达式和句子,然后用这题算法的改进版来判断相似度(大神勿喷)。相关文章推荐
- C/C++头文件注释
- 坚持不懈之linux haproxy 配置文件 详情
- windows 10 设置
- 位扩展和位截断
- [LeetCode] Search a 2D Matrix(二分查找)
- 不错的文章
- linux-fdisk/mke2fs/dumpe2fs/fsck
- linux内核移植和根文件系统制作(三)
- 安卓开发笔记二——软键盘弹出不遮挡布局的策略
- Selenium学习---(三)
- Mysql与Oracle区别
- 求某正整数重新打乱后最大数与最小数的差值
- 求二进制数中1的个数
- java基础-API-集合框架-Collection
- 指向数组的指针
- Linux下文本常用处理技巧
- HDU 杭电2501 Tiling_easy version【规律题】
- 【分享】もっと 姉、ちゃんとしようよっ!+アフターストーリー 【日文硬盘版】(带全CG存档&日文攻略
- 华为数字芯片工程师实习生面试全过程
- 👀