ARC #077 SS(含严格证明) - KMP

本文探讨了一种特定字符串操作问题,即通过不断重复一个最短周期来构造新的字符串,并分析了如何确定这一最短周期及其对生成字符串的影响。文章提供了一个算法实现方案,包括如何高效地计算最小正周期和如何进行字符串的扩展。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

结论题,假设那个串叫SS,并且操作一次后变成TT。
首先自己画画就会发现T比S多出来的那一段一定是S的一个周期。(注意S的周期和SS的周期不是一回事,比如样例)
前缀技能:如果a和b都是s的周期,那么(a, b)也是s的周期。因此s的最小正周期是s的任何一个周期的周期,证明类似辗转相除。
然后我们证明对于任意情况下,你选择的周期,记做g,越小越好,也就是选择最小周期g。否则,假设选择的是h是S的一个周期并且做出来的结果比g好。那么根据一些border的知识我们知道因为g是最小正周期所以h的长度是g的长度我的若干倍,那么显然h能做到的事情g也能做到。因此h不会比g好。
否则,我们证明这么两件事情:
1)f(SS)=SgSg
这个其实自己画画就能画出来。
2)若S的最小正周期是g,那么串Sg的最小正周期为:
a)g,如果g的长度是S的因数,这显然。
b)S,else。我们证明b)。
反证。首先S是Sg的一个周期。因此假定h是Sg的最小正周期,那么|h|< |S|,否则结论成立。那么根据border的知识,S的h的倍数。因此h也是S的一个周期,从因此g的长度是h的长度的因数,因此g的长度是S的长度的因数,这样就gg了。
我们考虑上述两个结论在干啥,根据1),我们知道就是,考虑这个偶串的前半段会不停的加上一个最小正周期。
如果g的长度是S的长度的因数,那么相当与每次在后面加入两个g,也就是最终序列全是g,自己特判掉就可以了。下文假设g的长度不是S的长度的因数
根据后半段,我们假设操作了x次后这个串的前半部分是Sg,S是操作第x-1次串,那么S是Sg的最小正周期,第x+1次操作后的串前半部分就是SgS,也就是每次操作得到的串都是上两次操作得到的串拼起来,就像一个斐数一样。
实现的时候其实不用特判整除的情况。

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#define N 200010
#define lint long long
#define debug(x) cerr<<#x<<"="<<x
#define sp <<" "
#define ln <<endl
using namespace std;
char s[N];int nxt[N];
struct E{
    lint len,cnt[26];E() { len=0,memset(cnt,0,sizeof cnt); }
    inline E operator=(const E &e)
    {   return len=e.len,memcpy(cnt,e.cnt,sizeof cnt),*this;    }
    inline E operator+(const E &e)const
    {
        E res;res=*this;
        for(int i=0;i<26;i++) res.cnt[i]+=e.cnt[i];
        return res.len+=e.len,res;
    }
    inline E operator-(const E &e)const
    {
        E res;res=*this;
        for(int i=0;i<26;i++) res.cnt[i]-=e.cnt[i];
        return res.len-=e.len,res;
    }
    inline E operator+=(const E &e) { return (*this)=(*this)+e; }
}a[1010];
inline int getg(char *s,int n)
{
    for(int i=2;i<=n;i++)
    {
        int &j=nxt[i];j=nxt[i-1];
        while(j&&s[j+1]!=s[i]) j=nxt[j];
        if(s[j+1]==s[i]) j++;
    }
    return n-nxt[n];
}
inline E query(lint n,int c)
{
    E ans;
    for(int i=c;i>=1;i--)
        if(n>=a[i].len) n-=a[i].len,ans+=a[i];
    for(int i=1;i<=n;i++) ans.cnt[s[i]-'a']++;
    return ans;
}
int main()
{
    scanf("%s",s+1);int n=(int)strlen(s+1),c=2;
    int k=n/2,g=getg(s,k);lint l,r;cin>>l>>r;
    for(int i=1;i<=k;i++) a[1].cnt[s[i]-'a']++;
    a[2]=a[1],a[1].len=k,a[2].len=k+g;
    for(int i=1;i<=g;i++) a[2].cnt[s[i]-'a']++;
    while(a[c-1].len+a[c].len<=r) c++,a[c]=a[c-1]+a[c-2];
    E ans=query(r,c);
    ans=ans-query(l-1,c);
    for(int i=0;i<26;i++) printf("%lld ",ans.cnt[i]);
    return !printf("\n");
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值