3189. 【GDOI2013模拟8】解密
题目
Description
Mirko要解一段加密文,但他只知道某一个句子是原文的一部分。你的任务是要在密文中找到第一个对应这个句子的地方。
文段是通过用某个单词(可能和原文一样的单词)替换原始文段每一个单词来加密的。如果某些单词在原文出现一次以上,就会使用相同的替换单词来替换。没有两个不同的单词使用相同的替换单词。
单词是通过空格隔开的小写字母序列。句子是连续单词的序列。
Input
第一行输入密文,这段文段不会有超过 1 0 6 10^6 106个字符,每个单词之间只会有一个空格字符,行末处输入"$","$"不是文段的一部分。
接下来一行输入出现在原文中的句子,这个句子就是我们要在密文中要找到的句子。句子不会超过 1 0 6 10^6 106个字符,而且符合上面所描述的格式。
Output
输出一行,包括第一个对应原始文段句子的第一个单词的位置。
Sample Input
输入1:
a a a b c d a b c $
x y $
输入2:
xyz abc abc xyz $
abc abc $
输入3:
a b c x c z z a b c $
prvi dr prvi tr tr x $
Sample Output
输出1:
3
输出2:
2
输出3:
3
Data Constraint
N ≤ 1000000 N≤1000000 N≤1000000
题解
- 第一眼看上去,难道不是KMP吗???
- 仔细看看,怎么实现呢,匹配串和原串的对应关系都不能确定,
- 那么其实还有一种很巧妙的方法,
- 把每个串转变为它离前面一个最近的和它相同的串的距离,
- 这里用哈希维护,把每个单词按 27 27 27进制压成一个数,一定要模一个特别大的质数,
- 可以用多个不同质数相乘再加 1 1 1,这样保证是个质数,一直乘到 1 0 12 10^{12} 1012左右。
- 然后KMP放在这样转化后的串中比较。
- 但有特殊情况,
- 原串:A A B
- 压缩:-1 1 -1
- 匹配串:A B
- 压缩:-1 -1
- 如果就这么直接匹配是无解的,
- 所以“-1”的匹配方式不同,
- KMP中有一个匹配的指针 j j j,表示当前对应匹配串的第 j j j位,
- 只要原串的当前位的值是“-1”,或者值大于等于 j j j,即说明前面 1 − ( j − 1 ) 1-(j-1) 1−(j−1)位不曾出现过重复这个单词,也就可以满足匹配条件。
代码
#include<cstdio>
#include<cstring>
using namespace std;
#define LL long long
#define md 200560490131
#define mo 999983
char s[1000010];
LL e[1000010],h[2000010],sum[1000010];
int a[1000010],b[1000010],next[1000010];
int hash(LL t,int n)
{
LL x=t%mo;
while(h[x])
{
if(h[x]==t)
{
int p=sum[x];
sum[x]=n;
return p;
}
x=(x+1)%2000000;
}
h[x]=t;
sum[x]=n;
return 0;
}
int main()
{
int i,j,n=0,m=0;
e[0]=1;
for(i=1;i<=1000000;i++) e[i]=e[i-1]*27%md;
while(1)
{
scanf("%s",s+1);
if(s[1]=='$') break;
n++;
LL t=0;
for(i=1;i<=strlen(s+1);i++) t=(t+(s[i]-'a'+1)*e[i-1]%md)%md;
int p=hash(t,n);
if(p==0) a[n]=0; else a[n]=n-p;
}
scanf("
");
memset(h,0,sizeof(h));
memset(sum,0,sizeof(sum));
while(1)
{
scanf("%s",s+1);
if(s[1]=='$') break;
m++;
LL t=0;
for(i=1;i<=strlen(s+1);i++) t=(t+(s[i]-'a'+1)*e[i-1]%md)%md;
int p=hash(t,m);
if(p==0) b[m]=0; else b[m]=m-p;
}
j=0;
for(i=2;i<=m;i++)
{
while(j>0&&b[j+1]!=b[i]) j=next[j];
if(b[j+1]==b[i]) j++;
next[i]=j;
}
j=0;
for(i=1;i<=n;i++)
{
while(j>0&&!(b[j+1]==a[i]||(b[j+1]==0&&a[i]>j))) j=next[j];
if(b[j+1]==a[i]||(b[j+1]==0&&a[i]>j)) j++;
if(j==m)
{
printf("%d
",i-m+1);
return 0;
}
}
return 0;
}