Loading

洛谷P3426

Description

给定一个字符串,求其可重叠的最小循环节长度。

Analysis

思考一下,如果 \(A\)\(B\) 的最小可重叠循环节,那么 \(A\)\(B\) 的最初和最末位置都出现过。

假设 \(A\) 只在这两个位置出现过,也就是说 \(B\) 此时是由 \(\le 2\times|A|\) 个字符组成的。这时的 \(A\) 可以通过 KMP 直接求得。

如果我们再在 \(B\) 后加上一段新的字符,使得 \(A\) 出现了三次,但是 \(A\) 仍然是 \(B\) 的最小可重叠循环节。

这时的 \(A\) 虽然无法直接求得,但是修改后对答案无影响。

反过来说,如果是三个 \(A\) 组成了 \(B\),我们在 \(B\) 后删除一段字符使得 \(B\) 只由两个 \(A\) 组成,就可以 KMP直接求,且答案不会改变。

说明什么?我们的答案是从前面的状态推到了后面的状态,且前面的状态可以由 KMP 求出来。

接下来我们考虑递推的状态是什么。

Solution

我们设给出的字符串为 \(S\),长度为 \(n\)。设 \(f[i]\) 表示 \(S[1\dots i]\) 这一段的答案。

首先有一个结论:\(f[i]\) 只能是 \(i\) 或者 \(f[nxt[i]]\)

证明:

首先有两个显然结论:

  1. \(f[i]\) 一定 \(\le i\),因为最劣情况下,它可以自己覆盖自己。

  2. \(f[nxt[i]]\le f[i]\),因为 \(nxt[i]\le i\),它覆盖区间更短,所以它不可能更大。

剩下的考虑反证法。

  • \(f[i]<f[nxt[i]]\),则说明 \(f[i]\) 无法覆盖 \(S[1\dots nxt[i]]\),那么它显然也无法覆盖 \(S[1\dots i]\),所以不成立。
    并且 \(i>nxt[i]\)\(f[nxt[i]]\) 能覆盖 \(S[1\dots nxt[i]]\),所以 \(f[nxt[i]]\) 应该要更小。

  • \(f[nxt[i]]<f[i]<i\),则说明最长公共前后缀长度是 \(f[i]\)。但是根据 KMP 原理,\(nxt[i]\) 才是最长公共前后缀长度。
    又因为 \(f[nxt[i]]\le nxt[i]\),所以 \(f[i]>nxt[i]\),所以不成立。

综上,\(f[i]\) 只能是 \(i\) 或者 \(f[nxt[i]]\)

接下来考虑什么情况下可以确定它的具体取值。

\(f[i]=f[nxt[i]]\) 的充要条件是 \(j\in[i-nxt[i],i)\)\(f[j]=f[nxt[i]]\)

因为 \(f[nxt[i]]\) 可以覆盖前 \(nxt[i]\) 个字符,所以也可以覆盖后 \(nxt[i]\) 个字符。并且 \(f[j]\) 可以覆盖 \(S[1\dots j]\)

\(f[j]\ne f[nxt[i]]\),那么它连 \(S[1\dots nxt[i]]\) 都无法覆盖。

\(f[j]\notin [i-nxt[i],i)\),则至少有 \(S[j+1\dots i-nxt[i]]\) 这一段不会被覆盖。

所以判断一下存不存在一个答案是 \(f[nxt[i]]\) 的值存在于 \([i-nxt[i],i)\) 即可。

当然,还有一种情况:如果给出的字符串只包含一种字符,那么上面都是扯淡,直接输出 \(1\) 即可。

Code

#include<queue>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#define maxn 500010
#define INF 0x3f3f3f3f
//#define int long long

char s[maxn];
bool flag;
int n,ans,Fir,Sec;
int Nxt[maxn],f[maxn],pre[maxn];
//f[i] 表示 1 到 i 这段区间的最佳答案;
//pre[i] 表示答案 i 出现的最新位置,也就是说 pre[f[i]] 就是 f[i] 这个答案出现的最新位置qwq; 

int read(){
    int s=0,w=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
    while(ch>='0'&&ch<='9')s=(s<<1)+(s<<3)+ch-'0',ch=getchar();
    return s*w;
}

int main(){
    scanf("%s",s+1);n=strlen(s+1);
    for(int i=2;i<=n;i++) if(s[i]!=s[1]) flag=true;
    if(!flag){printf("1\n");return 0;}
    for(int i=2;i<=n;i++){
        while(Fir&&s[Fir+1]!=s[i]) Fir=Nxt[Fir];
        if(s[Fir+1]==s[i]) Fir++;Nxt[i]=Fir;    
    }
    for(int i=2;i<=n;i++){
        f[i]=i;
        if(pre[f[Nxt[i]]]>=i-Nxt[i]) f[i]=f[Nxt[i]];
        pre[f[i]]=i;
    }
    printf("%d",f[n]); 
    return 0;
}
posted @ 2021-06-09 20:34  KnightL  阅读(47)  评论(0编辑  收藏  举报