题解 P8085 [COCI2011-2012#4] KRIPTOGRAM

【洛谷博客】

思路并不是很直接的一道哈希题,并不算特别难,但也不简单。

分析

看到题意后有字符串匹配,很容易想到哈希。

因为每一个明文对应着一个密文,可以记当前单词距离到上一个相同单词的距离(如果没有即为 0),可以观察到明文和密文的单词距离序列如果能相同,就说明可以对应上。

为了快速判断这样一个单词距离序列相同,可以用哈希。

滑动查询区间的时候,需要移除新区间中被移除单词第一个位置的哈希值,这个用队列存一下就行。

时间复杂度 O(n),具体请看代码实现。

注意事项

因为使用了队列 std::queue,而默认的实现方式是 std::queue<,std::deque<> >,空间会爆炸,所以我们应该使用 std::queue<,std::list<> >

代码

//the code is from chenjh
#include<cstdio>
#include<cstring>
#include<iostream>
#include<list>
#include<queue>
#include<unordered_map>
#define MAXN 1000005
using namespace std;
typedef unsigned long long ULL;
const unsigned int p=131;
ULL pn[MAXN];
int n,m,t=0;
unordered_map<string,int> M;
int a[MAXN],b[MAXN],c[MAXN];//c[i] 表示第 i 个单词所造成的哈希贡献。
int ls[MAXN];//记录上一个单词出现的位置。
queue<int,list<int> > Q[MAXN];//存储每一个单词的位置队列信息。
int main(){
	for(string s;cin>>s&&s!="$";){
		if(M.find(s)==M.end()) M[s]=++t;
		a[++n]=M[s];
	}//将 a 的单词转化为数字,方便比较。
	pn[0]=1;
	for(int i=1;i<=n;i++) pn[i]=pn[i-1]*p;//预处理 p 的幂次方数组。
	M.clear(),t=0;//注意清空重新分配编号。
	for(string s;cin>>s&&s!="$";){
		if(M.find(s)==M.end()) M[s]=++t;
		b[++m]=M[s];
	}//将 b 的单词转化为数字,方便比较。
	ULL hsa=0,hsb=0;//a 的区间哈希值和 b 的哈希值。
	for(int i=1;i<=m;i++) hsb=hsb*p+(ls[b[i]]?i-ls[b[i]]:0),ls[b[i]]=i;//相减得到距离,找不到上一个则为 0.
	memset(ls,0,sizeof ls);
	for(int i=1;i<=m;i++){
		c[i]=ls[a[i]]?i-ls[a[i]]:0;
		hsa=hsa*p+c[i];
		Q[a[i]].push(ls[a[i]]=i);
	}
	if(hsa==hsb) return puts("1"),0;
	for(int i=m+1;i<=n;i++){
		Q[a[i-m]].pop();//弹出最前端单词。
		if(!Q[a[i-m]].empty()) hsa-=c[Q[a[i-m]].front()]*pn[i-Q[a[i-m]].front()-1];//删除新的队列中第一个被删除单词所贡献的哈希值,如果不存在则不需要删除。
		c[i]=(ls[a[i]]>i-m)?i-ls[a[i]]:0;
		hsa=hsa*p+c[i];//加入最新元素。
		Q[a[i]].push(ls[a[i]]=i);//加入新的单词。
		if(hsa==hsb) return printf("%d\n",i-m+1),0;
	}
	return 0;
}
posted @   Chen_Jinhui  阅读(17)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】

一言

十代目的意志就是我们的意志!
——家庭教师
点击右上角即可分享
微信分享提示