【CCPC】2021威海站 D. Period | 字符串循环节、Next数组

140 阅读4分钟

本文已参与「新人创作礼」活动, 一起开启掘金创作之路。

题目链接

Problem - D - Codeforces

题目

Zayin have learned how to compute the number of periods of a string recently.

As his girlfriend, Ziyin would like to give Zayin some strings and test whether he really learn the knowledge well. But Ziyin is too lazy to produce strings which are completely different, so she would firstly give Zayin a string, and ask him if she modify the i-th character of the string to #, what is the number of periods of the new string. (Note that Ziyin wouldn't really perform the modification)

It's really a big question for Zayin. Can you help him so that he would not lose face in front of his girlfriend?

题目大意

有一个字符串 s1,s2,...,sns_1,s_2,...,s_n 仅由小写字母组成,有 qq 个询问,每个询问把输入的位置 xx 上的字符改成 #,要求输出修改后的字符串有多少个循环节。(每次询问相互独立,即每次询问整个字符串中仅包含一个 #

一个正整数 T 是字符串 s1,s2,...,sns_1,s_2,...,s_n 的循环节,当且仅当 1T<n1≤T<n 且对于所有的 i(T,n]i∈(T,n] 均满足 si=siTs_i=s_{i−T}

思路

首先因为每次查询的字符串都包含一个 #,我们可以得知以下事实:

  1. 循环节一定大于 n2\frac{n}{2},否则 # 将会参与匹配,一定不合法。
  2. 则我们的问题转化为:计算位置 TT 的数量,满足在 TT 右侧把整个字符串斩成两段,第二段为第一段的前缀。
  3. 那么第一条就转化为要求第一段一定比第二段长,# 一定在第一段中。为了完成匹配,第二段长度不超过 x1x-1
  4. 则我们只需要在原串中找到所有的合法的 TT,然后对于每个询问看有多少个合法的 TT 不小于 max(x,nx+1)\max(x,n-x+1) 即可

为了便于求解,我们可以把第四条改成在原串中找到所有的合法的第二个串的长度,然后对于每个询问看有多少个合法的第二个串的长度不大于 min(x1,nx)\min(x-1,n-x)。只要找到合法的第二个串的长度,求解就可以简单的使用前缀和或者二分解决了,下面我们讨论怎样求解所有的合法的第二个串的长度。

我们定义合法的第二段串的长度仅需要满足把串切成两半后,一个是另一个的前缀。注意到我们最终输出的是多少个合法的第二个串的长度不大于 min(x1,nx)\min(x-1,n-x),当满足这个条件时,第一段一定比第二段长。所有条件均可以满足。

我们先使用 KMP 对输入的 s1,s2,...,sns_1,s_2,...,s_nnextnext 数组。next[i]next[i] 的含义是 s1,s2,...,sis_1,s_2,...,s_i 中,长度为 next[i]next[i] 的前缀和长度为 next[i]next[i] 的后缀完全相同,如下图所示:

image.png

黑框表示 s1,s2,...,sis_1,s_2,...,s_i,红框表示的字符串和蓝框表示的字符串完全相同。所以 next[n]next[n] 就是第一个合法的第二个串的长度了。又因为红框表示的字符串和蓝框表示的字符串完全相同,则在下图中:

image.png

  • 粉框和绿框的并集就是原来的蓝框所在的区域。
  • 又因绿框的右端点是 next[n]next[n],粉框的右端点是 next[next[n]]next[next[n]],所以由 nextnext 数组的定义,绿框表示的字符串和粉框表示的字符串完全相同。
  • 又因红框长度为 next[next[n]]next[next[n]] 的后缀与蓝框的后缀绿框完全相同,所以红框长度为 next[next[n]]next[next[n]] 的后缀和粉框完全相同。

所以 next[next[n]]next[next[n]] 也是一个合法的后缀长度………上述过程可以递归的进行。这说明 next[n],next[next[n]],next[next[next[n]]],...next[n],next[next[n]],next[next[next[n]]],... 均为合法的后缀长度,直到嵌套结果为 0 才截止。

但是我们会不会漏掉合法的第二个串的长度呢?显然 next[n],next[next[n]],...next[n],next[next[n]],... 是单调减少的。如果存在一个合法的第二个串的长度是 next[i]<b<inext[i]<b<i,则 image.png

  • 如果长度为 bb 的前缀与整个串长度为 bb 的后缀不一样,显然不满足要求。
  • 如果一样,则由于 ii 是不断跳 nextnext 得到的,所以整个串长度为 ii 的前缀与整个串长度为 ii 的后缀一样。又整个串长度为 bb 的前缀与整个串长度为 bb 的后缀一样,所以 s1,s2,...,sis_1,s_2,...,s_i 的长度为 bb 的后缀也和整个串长度为 bb 的前缀一样,即 next[i]=bnext[i]=b,与假设不符。

综上 next[n],next[next[n]],...next[n],next[next[n]],... 即为所有合法的第二个串的长度。

预处理结束后,对每个询问输出有多少个合法的第二个串的长度不大于 min(x1,nx)\min(x-1,n-x)

代码

#include <stdio.h>
#include <string.h>
#include <vector>
#include <algorithm>
using namespace std;
using LL=long long;
const int N=1000005;
char ch[N];
int n,nxt[N];
int sum[N], mx[N];

int main()
{
	scanf("%s",ch+1);
	n=strlen(ch+1);
	nxt[0]=-1;
	nxt[1]=0;
	for (int i=2,j=0;i<=n;++i)
	{
		while (ch[j+1]!=ch[i]&&j!=-1) j=nxt[j];
		nxt[i]=++j;
	}
	for (int now=nxt[n];now;) 
	{
		sum[now]++;
		now=nxt[now];
	}
	for (int i=2;i<=n;++i) sum[i]+=sum[i-1];
	int q,x;
	for (scanf("%d",&q);q--;)
	{
		scanf("%d",&x);
		printf("%d\n",sum[min(x-1,n-x)]);
	}
	return 0;
}