洛谷 P5334 [JSOI2019]节日庆典

考虑维护当前可能成为答案的后缀,因为当前的最小后缀加上前面一段后不一定仍最小,所以对于所有前缀为最小后缀的后缀都要进行考虑,但能成为答案的后缀并不是所有这样的后缀。

设当前考虑的串为 \(s\),后缀 \(a\) 能成为候选后缀当且仅当存在字符串 \(t\),使得 \(at\)\(st\) 的最小后缀。

有一个结论是:实际要考虑的候选后缀的个数为 \(O(\log n)\)。即对于候选后缀中的任意两个后缀 \(a,b\),满足 \(|a|<|b|\),则一定有 \(2|a|\leqslant |b|\)

用反证法来证明,若存在两个后缀 \(a,b\),满足 \(|a|<|b|<2|a|\)。因为 \(a\)\(b\)\(\text{border}\),所以 \(b\) 存在一个长为 \(|b|-|a|<\frac{|b|}{2}\) 的周期,设其为 \(u\),那么 \(a=uv,b=u^2v\),因为 \(a\) 在候选后缀中,所以存在字符串 \(t\),使得 \(at\)\(st\) 的最小后缀,得:

\[\large\begin{aligned} at&<bt\\ uvt&<u^2vt\\ vt&<uvt=at\\ \end{aligned} \]

因为 \(v\) 也是 \(s\) 的非空后缀,所以这与 \(at\)\(st\) 的最小后缀矛盾,因此这样的 \(a,b\) 不存在。

用扩展 \(KMP\) 预处理后就可以快速比较两个后缀。

#include<bits/stdc++.h>
#define maxn 3000010
using namespace std;
template<typename T> inline void read(T &x)
{
    x=0;char c=getchar();bool flag=false;
    while(!isdigit(c)){if(c=='-')flag=true;c=getchar();}
    while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
    if(flag)x=-x;
}
int n,l,r;
int z[maxn];
char s[maxn];
vector<int> ve;
int main()
{
    scanf("%s",s+1),z[1]=n=strlen(s+1);
    for(int i=2;i<=n;++i)
    {
        if(i<=r) z[i]=min(z[i-l+1],r-i+1);
        while(i+z[i]<=n&&s[i+z[i]]==s[z[i]+1]) z[i]++;
        if(i+z[i]-1>r) l=i,r=i+z[i]-1;
    }
    for(int i=1;i<=n;++i)
    {
        vector<int> tmp;
        ve.push_back(i);
        for(int j=0;j<ve.size();++j)
        {
            int p=ve[j];
            while(!tmp.empty()&&s[i]<s[tmp.back()+i-p]) tmp.pop_back();
            if(tmp.empty()||(s[i]==s[tmp.back()+i-p]&&2*(i-p+1)<=i-tmp.back()+1)) tmp.push_back(p);
        }
        ve=tmp;
        int pos=ve[0];
        for(int j=1;j<ve.size();++j)
        {
            int p=ve[j],x=pos+i-p+1;
            if(z[x]>=p-pos)
            {
                x=p-pos+1;
                if(z[x]<pos-1&&s[z[x]+1]>s[x+z[x]]) pos=p;
            }
            else if(s[z[x]+1]<s[x+z[x]]) pos=p;
        }
        printf("%d ",pos);
    }
    return 0;
}
posted @ 2020-12-11 21:23  lhm_liu  阅读(179)  评论(0编辑  收藏  举报