【字符hash】密码破译

题目

Description
某年某月某日, r 64 打开了某个无聊的密码网站。
作为一个热衷于破译密码的SB, r 64 的IQ是非常高的,但是,一个人是无法在
一瞬间完成无数份密码的破译工作的(也许两瞬间),所以这个任务就交给你和
他共同来完成。
一个密码对应一个 字符串 (仅包含 小写英文字母 ),令这个串为S。你成功地
破解了这个密码串,当且仅当你找到了一个 最短 的T,使得S = T K ,即T重复K次。
密码S的关键值即为T的 长度 。
这个网站的密码有一个特性,它是一个很长很长的 母密码串的一段 ,碰巧的
是,你无意当中窃取到了这个密码串。作为一台十分负责的计算机,请帮助 r 64 解
决这个问题。

Input
输入文件 password.in 共m + 3行。第一行为正整数n,第二行为一个长为n的字
符串s。第三行为一个正整数m,表示密码串的个数。第四至m + 3行每行两个正整
数L,R(L ≤ R),表示询问的密码串在母密码串中的位置。

Output
输出文件 password.out 共m行。每一行一个正整数,表示对应密码串的关键值。

Sample Input
6
aababa
3
1 3
1 2
3 6

Sample Output
3
1
2

Data Constraint
测试点编号
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 数据范围
n ≤ 100, m ≤ 100
n ≤ 100, m ≤ 100
n ≤ 1000, m ≤ 1000
n ≤ 4000, m ≤ 4000
n ≤ 5000, m ≤ 5000
n ≤ 5000, m ≤ 5000
n ≤ 40000, m ≤ 90000
n ≤ 70000, m ≤ 100000
n ≤ 100000, m ≤ 100000
n ≤ 100000, m ≤ 100000
n ≤ 300000, m ≤ 600000
n ≤ 300000, m ≤ 600000
n ≤ 500000, m ≤ 2000000
n ≤ 500000, m ≤ 2000000
n ≤ 500000, m ≤ 2000000
n ≤ 500000, m ≤ 2000000
n ≤ 500000, m ≤ 2000000
n ≤ 500000, m ≤ 2000000
n ≤ 500000, m ≤ 2000000
n ≤ 500000, m ≤ 2000000

思路

考虑如何判断当前串的最短循环节,一个O(N)的做法是枚举长度,然后判断它是否为循环节。那么如何高效地判断循环节呢?还记得学过的KMP算法吗?串T有一个长为Len循环节的充要条件为T的前N – Len位与后N – Len位是一样的,而且满足Len | N。那么可以通过Hash直接比较。
接下来考虑优化暴力。假设一个长度为Len的前缀不是T的循环节,那么Len的所有约数肯定也不是,证明显然。所以我们可以确定每一个质因子出现了多少次,然后判断ans / 这个质因子是否为一个循环节,若是,则 ans /= 质因子。由于枚举的是每一个数的质因子,所以单次询问的复杂度为O(logN)。

代码

#pragma GCC optimize(2)
#include<bits/stdc++.h>
#define ll long long
const int N=6e5+77,mod=1e9+7;

int p[N],q[N],sum[N];

bool bo[N];

char ch[N];

inline ll power(ll x,ll t)
{
	ll b=1;
	while(t)
	{
		if(t&1) b=b*x%mod; x=x*x%mod; t>>=1;
	}
	return b;
}

inline int getsum(int l,int r)
{	
	return (sum[r]-1ll*sum[l-1]*power(256,r-l+1)%mod+mod)%mod;	
}

int main() {
	
	int n,m,num;
	
	scanf("%d\n%s\n%d",&n,ch+1,&m);
	
	sum[0]=0;
	
	for(int i=1; i<=n; ++i)
	
		sum[i]=(1ll*sum[i-1]*256+ch[i])%mod;
	
	num=0;
	
	for(int i=2; i<=n; ++i) {
		
		if(!bo[i]) {
			
			p[++num]=i;
			
			q[i]=num;
			
		}
		
		for(int j=1; j<=num&&p[j]*i<=n; ++j) {
			
			bo[i*p[j]]=1;
			
			q[i*p[j]]=j;
			
			if(i%p[j] == 0)
			
				break;
			
		}
		
	}

	for(int i=1; i<=m; ++i) {
		
		int l,r;
		
		scanf("%d %d",&l,&r);
		
		int len=r-l+1;
		
		for(int j=len; j > 1; j /= p[q[j]])
		{
			int sum1=getsum(l+len/p[q[j]],r);
			int sum2=getsum(l,r-len/p[q[j]]);
			if(sum1 != sum2) continue;
			len /= p[q[j]];	
		}
		printf("%d\n",len);
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值