0%

题目内容

题目描述

给你一个字符串 s1s_1,它是由某个字符串 s2s_2 不断自我连接形成的(保证至少重复 22 次)。但是字符串 s2s_2 是不确定的,现在只想知道它的最短长度是多少。

输入格式

第一行一个整数 LL,表示给出字符串的长度。

第二行给出字符串 s1s_1 的一个子串,全由小写字母组成。

输出格式

仅一行,表示 s2s_2 的最短长度。

输入输出样例 #1

输入 #1

1
2
8
cabcabca

输出 #1

1
3

说明/提示

样例输入输出 1 解释

对于样例,我们可以利用 abc\texttt{abc} 不断自我连接得到 abcabcabcabc\texttt{abcabcabcabc},读入的 cabcabca\texttt{cabcabca},是它的子串。

规模与约定

对于全部的测试点,保证 1L1061\le L \le 10^6

思路讲解

给出子串,找出最短循环节。

最大公共前后缀。

结论就是N-pi[N]。其实我也不是很懂。

不能够使用二分,因为周期并没有单调性

image

把这个列出来以后,我们就知道了周期 p 的成立条件是什么。可以通过枚举 p,非常简单的使用字符串哈希检验答案。

AC代码

https://www.luogu.com.cn/record/221541535

心路历程(WA,TLE,MLE……)

思路讲解

https://cp-algorithms.com/string/prefix-function.html

【KMP 学一遍忘一遍?ACM 金牌选手用可视化直击本质,理解了内核后想忘记都难!】 【精准空降到 04:05】 https://www.bilibili.com/video/BV1Er421K7kF/?share_source=copy_web&vd_source=6ca0bc05e7d6f39b07c1afd464edae37&t=245

那么我们容易发现,这个

image

ABC……ABC+D,那么如果前缀字符串后面跟着D,那么就是pi函数=4,但是如果后面不是D,那要怎么办?

不难发现,S[i+1]=S[π[i]+1]π[i+1]=π[i]+1S[i+1]=S[\pi[i]+1] \Rightarrow\pi[i+1]=\pi[i]+1(注意,这里我采用的是1-based,这更自然)。这不言而喻。(上面的这个图片已经解释了,我们再细化一下)

image

当然,这个是皆大欢喜的情况,但是如果出现失配了怎么办?

image

这个 pi_next(),为什么一定是这个 pi_next()。可以使用反证法

image

okay,问题就来到了,怎么样使用前缀数组实现这个文本串和模式串的这个匹配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//                  文本串             模式串
vector<ll> kmp(const string &s1,const string &s2){ // 返回总前缀数组pi
string S;
S='#'+s2+'#'+s1; // 采用1-based
N=SZ(S)-1;
vector<ll> pi(N+1,0);
FOR(i,1,N){
if(i==1) continue;
ll len=pi[i-1];
// if(S[i]==S[len+1]){
// pi[i]=len+1;
// }
while(true){
if(S[i]==S[len+1]){
pi[i]=len+1;
break;
}
if(len==0) break;
len=pi[len];
}
}
return pi;
}

特别的,我们认为 i=1 的时候 pi[i]=0。

他采用了 0-based,意思和我一样。

image

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

/*
* 采用 0-based 设计
* 模式串(s2)+'#'+文本串(s1) 需要这样子传入,如果想要执行 kmp 的话
* kmp 模板题 AC https://www.luogu.com.cn/record/269142391
*/
vector<ll> kmp(const string &A) {
// 返回总前缀数组pi
// 其定义是【1;i】真前后缀相同的最长长度
// 特别的,pi【1】=0
vector<ll> pi(SZ(A));
for (int i = 1; i < SZ(A); ++i) {
ll len = pi[i - 1];
while (len > 0 && A[len] != A[i]) {
len = pi[len - 1];
}
if (A[len] == A[i]) {
pi[i] = len + 1;
}
}
return pi;
}

void Solve() {
string s1, s2;
cin >> s1 >> s2;
vector<ll> pi = kmp(s2 + "#" + s1);
for (int i = SZ(s2); i < SZ(pi); ++i) {
if (pi[i] == SZ(s2)) {
ll ans = i - SZ(s2) - SZ(s2);
++ans;
cout << ans << "\n";
}
}
for (int i = 0; i < SZ(s2); ++i) {
cout << pi[i] << " ";
}
cout << "\n";
}

AC代码

AC
https://www.luogu.com.cn/record/269142391

心路历程(WA,TLE,MLE……)

思路讲解

那么容易发现,其实这种区间操作的题目一般来说最短的操作是最灵活的,一个长操作也可以化为多个短操作的结果。

比如说这道题目长度为4的操作可以变为对中间两个元素执行一次,再对两侧两个元素分别执行一次。

所以我们不用考虑长度>2的操作。

那么不难注意到,就是说这个开头和结尾往往是这个破局的地方,那么开头那如果不同,必须要操作后面一个,然后不断传递,最后传递到A_[N],就结束了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
FOR(i,1,N){
cin>>A[i];
A_[i]=A[i];
}
FOR(i,1,N){
cin>>B[i];
}
FOR(i,1,N-1){
ll diff=B[i]-A_[i];
A_[i+1]+=diff;
}
if(A_[N]==B[N]){
cout<<"Yes\n";
}else{
cout<<"No\n";
}

AC代码

https://www.luogu.com.cn/record/221383853

心路历程(WA,TLE,MLE……)

思路讲解

AC代码

心路历程(WA,TLE,MLE……)

思路讲解

由于奇数环既有一条偶数路径,又有一条奇数路径,可以直接特判。

AC代码

https://www.luogu.com.cn/record/221314756

心路历程(WA,TLE,MLE……)