题意
有一个字符串 s s s( Σ = 26 \Sigma=26 Σ=26),记 f ( t ) f(t) f(t) 为 t t t 在 s s s 中出现的次数,有 m m m 次以下操作:
- addl c \operatorname{addl}\ c addl c:在 s s s 左侧添加字符 c c c;
- addr c \operatorname{addr}\ c addr c:在 s s s 右侧添加字符 c c c;
- transl l 1 r 1 l 2 r 2 \operatorname{transl}\ l_1\ r_1\ l_2\ r_2 transl l1 r1 l2 r2:询问:假如通过在 s [ l 1 … r 1 ] s[l_1\dots r_1] s[l1…r1] 左侧添加或删除字符,在最小步数内使其变成 s [ l 2 … r 2 ] s[l_2\dots r_2] s[l2…r2],在这过程中出现的所有回文串 t t t 的 ∣ t ∣ f ( t ) |t|f(t) ∣t∣f(t) 之和;
- transr l 1 r 1 l 2 r 2 \operatorname{transr}\ l_1\ r_1\ l_2\ r_2 transr l1 r1 l2 r2:询问:假如通过在 s [ l 1 … r 1 ] s[l_1\dots r_1] s[l1…r1] 右侧添加或删除字符,在最小步数内使其变成 s [ l 2 … r 2 ] s[l_2\dots r_2] s[l2…r2],在这过程中出现的所有回文串 t t t 的 ∣ t ∣ f ( t ) |t|f(t) ∣t∣f(t) 之和。
例:把
abb
给 transl \operatorname{transl} transl 成dbbcbb
,中途出现的字符串有:abb -> bb -> cbb -> bcbb -> bbcbb -> dbbcbb
,其中bb
与bbcbb
是回文的,答案为 ∣ bb ∣ f ( bb ) + ∣ bbcbb ∣ f ( bbcbb ) |\texttt{bb}|f(\texttt{bb})+|\texttt{bbcbb}|f(\texttt{bbcbb}) ∣bb∣f(bb)+∣bbcbb∣f(bbcbb)。
题解
首先明确一点:正、反串的 PAM 形态是一样的:
考虑到回文串正着看、反着看都一样,实际上回文串的最长回文前缀,也就是其最长回文后缀。所以 f a i l ′ = f a i l fail'=fail fail′=fail,只维护 f a i l fail fail 即可。
(从自己博客抄的)
我们先只考虑 transl \operatorname{transl} transl,另一个方向同理。显然在 transl \operatorname{transl} transl 中出现过的字符串为 s [ l 1 … r 1 ] s[l_1\dots r_1] s[l1…r1] 的所有长度在 r 1 − l 1 + 1 r_1-l_1+1 r1−l1+1 与 ∣ lcs (最长公共后缀) ( s [ l 1 … r 1 ] ) , s [ l 2 … r 2 ] ) ∣ |\operatorname{lcs}\text{\footnotesize(最长公共后缀)}(s[l_1\dots r_1]),s[l_2\dots r_2])| ∣lcs(最长公共后缀)(s[l1…r1]),s[l2…r2])∣ 之间的后缀、以及 s [ l 2 … r 2 ] s[l_2\dots r_2] s[l2…r2] 的所有长度在 r 2 − l 2 + 1 r_2-l_2+1 r2−l2+1 与 ∣ lcs ( s [ l 1 … r 1 ] ) , s [ l 2 … r 2 ] ) ∣ |\operatorname{lcs}(s[l_1\dots r_1]),s[l_2\dots r_2])| ∣lcs(s[l1…r1]),s[l2…r2])∣ 之间的后缀。假如不考虑回文,那这就是在 SAM 的 fail 树上的一条路径;考虑回文,那这就是在 PAM 的 fail 树上的一条路径,我们要统计其 l e n × f len\times f len×f 之和。
找到这条路径,首先要找到路径的两个端点。我们先把询问离线下来,把询问的所有位置改为以最终串为标准的位置,建出最终串的 PAM,再倒着“建”一遍,处理出以 p o s pos pos 为开头以及结尾的最长回文串。 transl \operatorname{transl} transl 时,找到 r 1 r_1 r1 结尾的最长回文子串,倍增沿 fail 往上跳(也可以树剖维护)到第一个长度不大于 r 1 − l 1 + 1 r_1-l_1+1 r1−l1+1 的回文串,作为一个端点( r 2 r_2 r2 同理)。注意 LCA 可能不统计入内,此时 LCA 对应的回文串不是两询问串的 lcs,判断时只需要看 s [ r 1 − L C A . l e n ] s[r_1-LCA.len] s[r1−LCA.len] 与 s [ r 2 − L C A . l e n ] s[r_2-LCA.len] s[r2−LCA.len] 是否相等即可。
考虑如何维护 l e n × f len\times f len×f。首先将最终串的 fail 树建出来,新加入一个字符(假设在左边)时,找到以该字符为开头的最长回文串,倍增沿 fail 往上跳(也可以树剖维护)到首个长度不超过当前串长的节点,则这个节点到根路径上所有节点的出现次数都会加一。于是我们要维护的有:到根路径上节点的 f f f 加一、询问一条路径的 f × l e n f\times len f×len 之和。可以用树剖或 LCT 维护。
综上:离线 + PAM + 倒着跑一遍 PAM + 树剖 + 倍增 预处理;修改操作先倍增向上跳,再修改到根路径;查询操作先倍增向上跳,再查询两点间路径,并特判 LCA。
代码有点长,不过不算很难写:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
int getint(){
int ans=0,f=1;
char c=getchar();
while(c<'0'||c>'9'){
if(c=='-')f=-1;
c=getchar();
}
while(c>='0'&&c<='9'){
ans=ans*10+c-'0';
c=getchar();
}
return ans*f;
}
const int N=3e5+10,L=19,S=30;
int s[N],l,r;
int n;
/***** PAM *****/
int len[N],ch[N][S],fail[N],cnt;
int lst,tsl;
int pre[N],suf[N];
void init_pam(){ len[1]=-1;fail[1]=fail[0]=1;cnt=2; }
void extend(int c,int pos){
int cur=lst;
while(s[pos-len[cur]-1]!=c)cur=fail[cur];
if(!ch[cur][c]){
int t=cnt++;
len[t]=len[cur]+2;
fail[t]=fail[cur];
while(s[pos-len[fail[t]]-1]!=c)fail[t]=fail[fail[t]];
fail[t]=ch[fail[t]][c];
ch[cur][c]=t;
}
lst=ch[cur][c];
pre[pos]=lst;
//if(len[lst]==size)tsl=lst;
}
void dnetxe(int c,int pos){
int cur=tsl;
while(s[pos+len[cur]+1]!=c)cur=fail[cur];
if(!ch[cur][c]){
int t=cnt++;
len[t]=len[cur]+2;
fail[t]=fail[cur];
while(s[pos+len[fail[t]]+1]!=c)fail[t]=fail[fail[t]];
fail[t]=ch[fail[t]][c];
ch[cur][c]=t;
}
tsl=ch[cur][c];
suf[pos]=tsl;
//if(len[tsl]==size)lst=tsl;
}
/***** Tree *****/
struct bian{
int e,n;
};
bian b[N<<1];
int st[N],tot=0;
void add(int x,int y){
tot++;
b[tot].e=y;
b[tot].n=st[x];
st[x]=tot;
}
int dep[N],f[L][N],sz[N],mxson[N];
int dfn[N],dfnend[N],nfd[N],top[N],dfnn=0;
void ss(int x){
sz[x]=1;
mxson[x]=cnt+10;
for(int i=st[x];i;i=b[i].n){
f[0][b[i].e]=x;
dep[b[i].e]=dep[x]+1;
ss(b[i].e);
sz[x]+=sz[b[i].e];
if(sz[b[i].e]>sz[mxson[x]])mxson[x]=b[i].e;
}
}
void ss2(int x,int t){
dfn[x]=dfnend[x]=++dfnn;
nfd[dfn[x]]=x;
top[x]=t;
if(mxson[x]<cnt)ss2(mxson[x],t);
for(int i=st[x];i;i=b[i].n){
if(b[i].e==mxson[x])continue;
ss2(b[i].e,b[i].e);
}
}
void init_lca(){
for(int i=1;i<L;i++){
for(int j=0;j<cnt;j++){
f[i][j]=f[i-1][f[i-1][j]];
}
}
}
int lca(int x,int y){
if(dep[x]<dep[y])swap(x,y);
for(int i=L-1;i>=0;--i){
if(dep[f[i][x]]>=dep[y])x=f[i][x];
}
if(x==y)return x;
for(int i=L-1;i>=0;--i){
if(f[i][x]!=f[i][y])x=f[i][x],y=f[i][y];
}
return f[0][x];
}
/***** Segment tree *****/
ll a[N<<2],tag[N<<2];
ll sum[N];
void init_sum(){
for(int i=1;i<=dfnn;i++)sum[i]=sum[i-1]+len[nfd[i]];
}
void pushdown(int x,int l,int r){
int mid=l+r>>1;
tag[x<<1]+=tag[x];
tag[x<<1|1]+=tag[x];
a[x<<1]+=(sum[mid]-sum[l-1])*tag[x];
a[x<<1|1]+=(sum[r]-sum[mid])*tag[x];
tag[x]=0;
}
void pushup(int x){
a[x]=a[x<<1]+a[x<<1|1];
}
void modify(int l,int r,int val,int x,int nl,int nr){
if(nr<l||nl>r)return;
if(l<=nl&&nr<=r){
a[x]+=(sum[nr]-sum[nl-1])*val;
tag[x]+=val;
return;
}
pushdown(x,nl,nr);
int mid=nl+nr>>1;
modify(l,r,val,x<<1,nl,mid);
modify(l,r,val,x<<1|1,mid+1,nr);
pushup(x);
}
ll query(int l,int r,int x,int nl,int nr){
if(nr<l||nl>r)return 0;
if(l<=nl&&nr<=r) return a[x];
pushdown(x,nl,nr);
int mid=nl+nr>>1;
ll ans=query(l,r,x<<1,nl,mid)+query(l,r,x<<1|1,mid+1,nr);
return ans;
}
void modify(int x,int val){
while(x<cnt){
int t=top[x];
modify(dfn[t],dfn[x],val,1,1,dfnn);
x=f[0][t];
}
}
ll query(int x){
ll ans=0;
while(x<cnt){
int t=top[x];
ans+=query(dfn[t],dfn[x],1,1,dfnn);
x=f[0][t];
}
return ans;
}
/***** Operations *****/
void add(char di,int pos,int len){
pos=(di=='l'?suf[pos]:pre[pos]);
for(int i=L-1;i>=0;--i)
if(::len[f[i][pos]]>len)pos=f[i][pos];
if(::len[pos]>len)pos=f[0][pos];
modify(pos,1);
}
ll transl(int r1,int r2,int len1,int len2){
int r11=r1,r22=r2;
r1=pre[r1],r2=pre[r2];
for(int i=L-1;i>=0;--i){
if(len[f[i][r1]]>len1)r1=f[i][r1];
if(len[f[i][r2]]>len2)r2=f[i][r2];
}
if(len[r1]>len1)r1=f[0][r1];
if(len[r2]>len2)r2=f[0][r2];
int l=lca(r1,r2);
if(len[l]!=len1&&len[l]!=len2&&s[r11-len[l]]==s[r22-len[l]]){
return query(r1)+query(r2)-query(l)*2;
}else{
return query(r1)+query(r2)-query(l)-query(f[0][l]);
}
}
ll transr(int l1,int l2,int len1,int len2){
int l11=l1,l22=l2;
l1=suf[l1],l2=suf[l2];
for(int i=L-1;i>=0;--i){
if(len[f[i][l1]]>len1)l1=f[i][l1];
if(len[f[i][l2]]>len2)l2=f[i][l2];
}
if(len[l1]>len1)l1=f[0][l1];
if(len[l2]>len2)l2=f[0][l2];
int l=lca(l1,l2);
if(len[l]!=len1&&len[l]!=len2&&s[l11+len[l]]==s[l22+len[l]]){
return query(l1)+query(l2)-query(l)*2;
}else{
return query(l1)+query(l2)-query(l)-query(f[0][l]);
}
}
struct que{
int op;
int c;
int l1,r1,l2,r2;
};
que q[N];
char tmp[10];
int main(){
n=getint();int m=getint();
l=100001;r=l+n;
for(int i=l;i<r;i++){
s[i]=getint()+1;
}
for(int i=0;i<m;i++){
scanf("%s",tmp);
if(tmp[0]=='a'){
if(tmp[3]=='l')q[i].op=0,s[--l]=q[i].c=getint()+1;
else q[i].op=1,s[r++]=q[i].c=getint()+1;
}
if(tmp[0]=='t'){
if(tmp[5]=='l')q[i].op=2;
else q[i].op=3;
q[i].l1=l+getint()-1,q[i].r1=l+getint()-1;
q[i].l2=l+getint()-1,q[i].r2=l+getint()-1;
}
}
init_pam();
for(int i=l;i<r;i++)extend(s[i],i);
for(int i=r-1;i>=l;--i)dnetxe(s[i],i);
for(int i=0;i<cnt;i++){
if(fail[i]!=i)add(fail[i],i);
}
for(int i=0;i<L;i++)for(int j=0;j<N;j++)f[i][j]=cnt+10;
dep[1]=1;ss(1);ss2(1,1);
init_lca();
init_sum();
int le=100001,ri=le+n;
for(int i=le;i<ri;i++)add('r',i,i-le+1);
for(int i=0;i<m;i++){
if(q[i].op==0){
--le;add('l',le,ri-le);
}
if(q[i].op==1){
add('r',ri,ri-le+1);++ri;
}
if(q[i].op==2){
printf("%lld\n",
transl(q[i].r1,q[i].r2,q[i].r1-q[i].l1+1,q[i].r2-q[i].l2+1));
}
if(q[i].op==3){
printf("%lld\n",
transr(q[i].l1,q[i].l2,q[i].r1-q[i].l1+1,q[i].r2-q[i].l2+1));
}
}
return 0;
}