// // // // // // // // // // // // // //

A Horrible Poem

A Horrible Poem

题目:

给出一个由小写英文字母组成的字符串S,再给出q个询问,要求回答S某个子串的最短循环节
n≤500 000 表示S的长度。
q≤2 000 000 表示询问次数。

code:

/*
  Time: 1.10
  Worker: Blank_space
  Source: #10038. 「一本通 2.1 练习 4」A Horrible Poem
*/
/*---------------------------------------------------*/
#include<cstdio>
using namespace std;
/*---------------------------------------------------*/
const int base = 60;
const int A = 1e4 + 7;
const int B = 1e5 + 7;
const int C = 1e6 + 7;
const int D = 1e7 + 7;
const int mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;
/*---------------------------------------------------*/
long long n, q, h[B << 3], pow[B << 3], prim[B << 3], cnt, c[B << 3], ans;
bool p, vis[B << 3];
char s[B << 3];
/*---------------------------------------------------*/
int read()
{
	int x = 0, f = 1; char ch = getchar();
	while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
	while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
	return x * f;
}
void pair()
{
	for(int i = 2; i <= n; i++)	if(!vis[i])
		for(int j = 1; j * i <= n; j++)
			if(!vis[i * j]) vis[i * j] = 1, prim[i * j] = i;
}
bool check(int l, int r, int x)
{
	if((h[r - x] - h[l - 1] * pow[r - l - x + 1] % mod + mod) % mod != (h[r] - h[l + x - 1] * pow[r - l - x + 1] % mod + mod) % mod) return 0;
	return 1;
}
/*---------------------------------------------------*/
int main()
{
//	freopen("okr2.in", "r", stdin);
//	freopen("", "w", stdout);
	
	n = read(); scanf("%s", s + 1); q = read(); pow[0] = 1; pair();
	for(int i = 1; i <= n; i++) pow[i] = pow[i - 1] * base % mod, h[i] = (h[i - 1] * base % mod + s[i] - 96) % mod;
	for(int i = 1; i <= q; i++)
	{
		int l = read(), r = read();
		int len = ans = r - l + 1; p = 0;
		while(len > 1)
		{
			int j = ans / prim[len]; len /= prim[len];
			if(check(l, r, j)) ans = j;
		}
		printf("%lld\n", ans);
	}
	
//	fclose(stdin);
//	fclose(stdout);
}

思路:

首先 第一个想法是暴力枚举

对于每一个区间 枚举一个子串看是否为循环节 判断的时候我们可以采用哈希 循环枚举每一个长度的子串

很明显 时间复杂度起飞了

考虑优化

能够构成区间循环节的子串的长度一定能被整个区间的长度整除 可以省去不必要的判断

判断的过程不需要循环比较整个串 如果一个子串是循环节 很显然的 其必然是从区间左端开始 且满足 \(s_{(l, r - x)} == s_{(l + x, r)}\)

这样我们将每一次的判断优化为了 \(O(1)\)

确实多过了几个点 到现在已经有六十分了(其实我自己写的代码参考题解之前也只有六十分)

最后一个强力的优化

线筛:可能被选出的循环节的长度一定是长度的质因数

正确性:

循环节的长度若不是区间长度的质因数
若其是质因数的倍数 则一定能由选出的质因数构成
否则其必然无法被整个区间的长度整除

所以我们可以直接预处理出长度的质因数
直接枚举质因数 判断即可

posted @ 2021-01-10 13:56  Blank_space  阅读(126)  评论(0编辑  收藏  举报
// // // // // // //