Loading

Manacher 的一个优雅结论

做题遇到了一个 trick,想了两天,求助教练,LLM 无果后,在大佬的帮助下明白了。

考虑以下 Manacher 代码的改版:

int n=s.size(),C=0,R=-1;
vector<int> p(n);
for(int i=0;i<n;i++){

    if(i<=R){//就是这样收缩
        int j=2*C-i;
        p[i]=p[j];
        while(p[i]>R-i+1) p[i]--;
    }
    else p[i]=1;

    while(i-p[i]>=0 && i+p[i]<n && s[i-p[i]]==s[i+p[i]]) p[i]++;
    int right=i+p[i]-1;

    if(right>=R) C=i,R=right;
}

其中相对于正常的 Manacher,将取 min 换成了朴素的收缩。这种改版,支持代码完成更多操作,减少数据结构的使用。

其实,这版代码的时间复杂度是线性的

证明

也不知道之前怎么想出来的复杂野鸡证明还假了,现在补一个。
就是我们考虑找一个媒介做势能分析,就取维护的最靠右字符串长度好了,暂且叫它 \(\delta\)
那么考虑一次成功的扩张,\(\delta\) 会加二,但是由于

if(right>=R) C=i,R=right;

\(\delta\) 最多才会加二。

考虑缩小,当一个字符串从另一个字符串对称过来时,我们画个图。

Screenshot 2026-02-20 212702

那么需要缩短的,就是蓝色的这一段。

Screenshot 2026-02-20 212702

然后此时的最靠右字符串,也就是 \(\delta\),是白色这一段。

Screenshot 2026-02-20 212702

那么紫色字符串每收缩一次,白色字符串也会至少收缩一次,\(\delta\) 至少减一。

马拉车总共会扩张 \(\mathcal{O}(N)\) 次,注意到 \(\delta\) 一直非负,那么收缩操作最多也只能进行 \(\mathcal{O}(N)\) 次,总复杂度线性。

一道练习题

#include<bits/stdc++.h>
#define ll __int128
using namespace std;
/*
FACE THE FEAR, MAKE THE FUTURE.
P12977 泪雨 Namid[A]me
需要维护:括号数量,当前总贡献(合法子回文串括号数量之和),区间长度。
*/
const int N=5e6+7;
int n; string s;
ll p[N<<1],contr[N<<1],num[N<<1];
ll c,r,ans;

int skl;

void pre(){
    string t="$#";
    for(char ch:s) t+=ch,t+="#";
    t+="$"; s=t,n=s.size();
}

void work(){
    for(int i=1;i<n;i++){
        if(i<=r){
            int j=2*c-i;
            while(p[j]>r-i+1){
                if(s[j+p[j]-1]=='#'&&num[j]*2>=p[j]-1) contr[j]-=num[j];
                if(s[j+p[j]-1]=='?') num[j]-=2;
                p[j]--;
            } contr[i]=contr[j],num[i]=num[j],p[i]=p[j];
        }
        else p[i]=1,num[i]=(s[i]=='?');
        while(s[i-p[i]]!='$'&&s[i+p[i]]!='$'&&s[i-p[i]]==s[i+p[i]]){
            num[i]+=2*(s[i+p[i]]=='?'); p[i]++;
            if(s[i+p[i]-1]=='#') contr[i]+=(2*num[i]>=p[i]-1)*num[i];
        }
        if(i+p[i]-1>=r) r=i+p[i]-1,c=i;
        ans+=contr[i]*i/2;
    }
}

int main(){
    cin>>skl>>s;
    pre();
    work();
    if(ans==0) cout<<0;
    stack<int> stk;
    while(ans){
        stk.push(ans%10);
        ans/=10;
    } while(stk.size()) cout<<stk.top(),stk.pop();
    return 0;
}
posted @ 2026-02-20 15:57  Trent900  阅读(30)  评论(0)    收藏  举报