马拉车练习1

前言 记录下今天写的2个马拉车的习题

题目1

含义

给出一个字符串 s。求s 有多少对相交的回文子串。包含也算作相交。

思路

这个居然有2900有点离谱

本质上就是差分+马拉车的思想

正难则反,我们可以统计不相交的回文子串的对数,然后用总对数减去不相交的回文子串的对数即是答案

不相交的回文子串假设端点分别为 \(x1,y1,x2,y2\) 那么一定有$ x1\leq y1 <\ x2 \leq y2$

我们只要统计出以i 为起点的回文串个数 st[i],和以 i为终点的回文串个数 ed[i]。然后计算

\(\sum\limits_{i=1}^{n} ed[i] \sum\limits_{j=i+1}^nst[j]\)

在使用 manacher 算法的时候,对每个 i 都计算出了 p[i] 那么我们就要把[i-p[i]+1, i+p[i]-1] 这个极大回文子串

对 st和 ed 的贡献算进去。差分维护即可

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
//typedef pair<int,int> pii;
#define fi first
#define se second
#define debug printf("aaaaaaaaaaa\n");
const int maxn=4e6+5,inf=0x3f3f3f3f,mod=51123987;
const ll INF=0x3f3f3f3f3f3f3f3f;
const double eps=1e-7;
char temp[maxn];
char s[maxn];
int n,tempn;
int p[maxn];
ll pre1[maxn],pre2[maxn];
int main(){
    scanf("%d %s",&tempn,temp+1);
    n=2*tempn+1;
    s[0]='~';
    for(int i=1;i<=n;i++){
        if(i%2==0) s[i]=temp[i/2];
        else s[i]='|';
    }
    int id=1,mx=1;
    ll ans=0;
    for(int i=1;i<=n;i++){
        if(mx>=i){
            p[i]=min(p[2*id-i],mx-i+1);
        }
        while(s[i+p[i]]==s[i-p[i]]) p[i]++;
        if(p[i]+i-1>=mx){
            mx=p[i]+i-1;
            id=i;
        }
        ans+=((p[i]-1)+1)/2;
        if(s[i]=='|'&&p[i]==1) continue;
        pre1[i-p[i]+1]++;
        pre1[i+1]--;
        pre2[i]++;
        pre2[i+p[i]]--;
    }
    ans%=mod;
    ans=ans*(ans-1)/2%mod;
    for(int i=1;i<=n;i++){
        pre1[i]+=pre1[i-1];
        pre2[i]+=pre2[i-1];
    }
    for(int i=1;i<=n;i++){
        if(i%2==1) pre2[i]=pre1[i]=0;
    }
    for(int i=n-1;i>=1;i--){ // 终点
        pre1[i]+=pre1[i+ 1];
        pre1[i]=(pre1[i]%mod+mod)%mod;
    }
    for(int i=1;i<=n;i++){
        if(s[i]=='|'&&i-1>=1&&i+1<=n){
            ans-=(pre2[i-1]*pre1[i+1])%mod;
            ans%=mod;
        }
    }
    printf("%lld\n",(ans%mod+mod)%mod);
    return 0;
}

题目2

含义

定义双回文串 T,满足存在 T = ab,其中 a 和 b 都是回文串。给定字符串 S,求一个 S 的最长的双回文子T。

|S| ≤ 10^6

思路

我们可以枚举 ab 中间的分界线。然后两边分别计算能延伸出去的最长回文子串长度,然后相加即可

维护以\(i\)开头的最长回文串,维护以\(i\)结尾的最长回文串

很容易想到使用马\(O(n^2)\)的算法,但其实对于每个\(i\)可以只更新以\(i\)为中点的回文串的边界点的值

然后最后再遍历一边rr[i]=max(rr[i],rr[i−2]−2)和ll[i]=max(ll[i],ll[i+2]−2)

给那些被半径直接略过的位置赋值,如:aabaa可能第五个位置ll被赋值,但第四个位置的a未被赋值。。。

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
#define fi first
#define se second
#define debug printf("aaaaaaaaaaa\n");
const int maxn=2e5+5,inf=0x3f3f3f3f,mod=51123987;
const ll INF=0x3f3f3f3f3f3f3f3f;
const double eps=1e-7;
char s[maxn],t[maxn];
int ns,nt;
int bg[maxn],ed[maxn];
int p[maxn];
int main(){
    scanf("%s",s+1);
    ns=strlen(s+1);
    nt=2*ns+1;
    t[0]='~';
    for(int i=1;i<=nt;i++){
        if(i%2){
            t[i]='|';
        }else{
            t[i]=s[i/2];
        }
    }
    int mx=1,id=1;
    for(int i=1;i<=nt;i++){
        if(mx>=i) p[i]=min(mx-i+1,p[2*id-i]);
        while(t[i+p[i]]==t[i-p[i]]) p[i]++;
        if(i+p[i]-1>=mx) mx=i+p[i]-1,id=i;
        bg[i-p[i]+2]=max(bg[i-p[i]+2],p[i]-1);
        ed[i+p[i]-2]=max(ed[i+p[i]-2],p[i]-1);
    }
    for(int i=2;i<=nt;i+=2){
        bg[i]=max(bg[i],bg[i-2]-2);
    }
    for(int i=nt-2;i>=2;i-=2){
        ed[i]=max(ed[i],ed[i+2]-2);
    }
    int ans=0;
    for(int i=2;i<=nt-2;i+=2){
        ans=max(ans,ed[i]+bg[i+2]);
    }
    printf("%d\n",ans);
    return 0;
}
posted @ 2021-07-27 22:10  hunxuewangzi  阅读(51)  评论(0编辑  收藏  举报