前言 记录下今天写的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;
}