1. 程式人生 > >【XSY2715】回文串 樹鏈剖分 回文自動機

【XSY2715】回文串 樹鏈剖分 回文自動機

保存 space 上一個 times struct while 允許 cpp return

題目描述

  有一個字符串\(s\),長度為\(n\)。有\(m\)個操作:

  • \(addl ~c\):在\(s\)左邊加上一個字符\(c\)
  • \(addr~c\):在\(s\)右邊加上一個字符
  • \(transl~l_1~r_1~l_2~r_2\):有兩個\(s\)的子串\(s_1=s[l_1\ldots r_1],s_2=s[l_2\ldots r_2]\)。你要把\(s_1\)變成\(s_2\)。每次允許在左邊加一個字符或刪一個字符。要求操作次數最少。定義一個字符串是好的當且僅當這個字符串是回文串且在操作過程中出現。記

\[ ans=\sum_{i=1}^n\sum_{j=i}^n[s[i\ldots j]~is~good]\times (j-i+1) \]

  求\(ans\)

  • \(transr~l_1~r_1~l_2~r_2\):和上一個操作類似,但是只允許在右邊加入或刪除字符

\[ <Empty \space Math \space Block> \]

  \(n,m\leq 100000\)

題解

  毒瘤題。

  先把所有操作保存下來,求出最終的字符串。

  觀察到操作過程中每個字符串都只會出現一次,而且左邊的一部分相同。

  可以用回文自動機完成操作。

  先把回文自動機建出來,把fail樹剖分。

  處理出每個前綴/後綴的最長回文子串。

  詢問時先在fail樹上面跳得到在範圍內的最長回文子串。

  那麽答案就是這條鏈上所有回文串的出現次數\(\times\)

長度的和。

  如果lca不是這兩個串的lcp,那麽就要減掉lca的值。

  插入時找到在當前範圍內的最長回文前綴/後綴,把這個點到根上所有字符串的出現次數\(+1\)

  時間復雜度:\(O((n+m)\log^2 n)\)

  其實開始給你的那個字符串可以在\(O(n)\)內預處理完成的,總的復雜度就是\(O(n+m\log^2 n)\),不過我懶得寫了。

代碼

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
namespace
pam { int s[200010]; int next[200010][30]; int fail[200010]; int len[200010]; int last; int n; int p; void init(int x) { int i; for(i=1;i<=26;i++) next[x][i]=2; } void init() { init(1); init(2); len[1]=-1; fail[1]=2; len[2]=0; fail[2]=1; s[0]=0; p=2; n=0; last=2; } void insert(int x) { s[++n]=x; while(s[n-len[last]-1]!=s[n]) last=fail[last]; int cur=last; if(next[cur][x]==2) { int now=++p; init(now); len[now]=len[cur]+2; last=fail[last]; while(s[n-len[last]-1]!=s[n]) last=fail[last]; fail[now]=next[last][x]; next[cur][x]=now; } last=next[cur][x]; } void rebuild() { last=2; n=0; } void insert2(int x) { s[++n]=x; while(s[n-len[last]-1]!=s[n]) last=fail[last]; int cur=last; last=next[cur][x]; } } namespace sgt { struct node { int l,r,ls,rs; ll v; ll s; int t; }; node a[400010]; int n; int rt; void build(int &p,int l,int r,int *v) { p=++n; a[p].l=l; a[p].r=r; if(l==r) { a[p].v=v[l]; return; } int mid=(l+r)>>1; build(a[p].ls,l,mid,v); build(a[p].rs,mid+1,r,v); a[p].v=a[a[p].ls].v+a[a[p].rs].v; } void add(int p,int t) { a[p].t+=t; a[p].s+=a[p].v*t; } void push(int p) { if(a[p].t) { add(a[p].ls,a[p].t); add(a[p].rs,a[p].t); a[p].t=0; } } void add(int p,int l,int r,int v) { if(l<=a[p].l&&r>=a[p].r) { add(p,v); return; } push(p); int mid=(a[p].l+a[p].r)>>1; if(l<=mid) add(a[p].ls,l,r,v); if(r>mid) add(a[p].rs,l,r,v); a[p].s=a[a[p].ls].s+a[a[p].rs].s; } ll query(int p,int l,int r) { if(l<=a[p].l&&r>=a[p].r) return a[p].s; int mid=(a[p].l+a[p].r)>>1; push(p); ll s=0; if(l<=mid) s+=query(a[p].ls,l,r); if(r>mid) s+=query(a[p].rs,l,r); return s; } } using sgt::rt; namespace tree { struct graph { int h[200010]; int v[200010]; int t[200010]; int n; void add(int x,int y) { n++; v[n]=y; t[n]=h[x]; h[x]=n; } }; graph g; void addedge(int x,int y) { g.add(x,y); } int f[200010]; int d[200010]; int w[200010]; int t[200010]; int s[200010]; int ms[200010]; int c[200010]; int v[200010]; int e[200010]; int ti; void dfs1(int x,int fa,int dep) { f[x]=fa; d[x]=dep; s[x]=1; int mx=0; int i; for(i=g.h[x];i;i=g.t[i]) { dfs1(g.v[i],x,dep+1); s[x]+=s[g.v[i]]; if(s[g.v[i]]>mx) { mx=s[g.v[i]]; ms[x]=g.v[i]; } } } void dfs2(int x,int top) { t[x]=top; w[x]=++ti; c[ti]=x; e[ti]=v[x]; if(!ms[x]) return; dfs2(ms[x],top); int i; for(i=g.h[x];i;i=g.t[i]) if(g.v[i]!=ms[x]) dfs2(g.v[i],g.v[i]); } int find(int x,int y) { while(v[t[x]]>y) x=f[t[x]]; return c[upper_bound(e+w[t[x]],e+w[x]+1,y)-e-1]; } void add(int x,int v) { while(x) { sgt::add(rt,w[t[x]],w[x],v); x=f[t[x]]; } } ll query(int x,int y) { ll s=0; while(t[x]!=t[y]) if(d[t[x]]>d[t[y]]) { s+=sgt::query(rt,w[t[x]],w[x]); x=f[t[x]]; } else { s+=sgt::query(rt,w[t[y]],w[y]); y=f[t[y]]; } if(w[x]<w[y]) s+=sgt::query(rt,w[x],w[y]); else s+=sgt::query(rt,w[y],w[x]); return s; } int getlca(int x,int y) { while(t[x]!=t[y]) if(d[t[x]]>d[t[y]]) x=f[t[x]]; else y=f[t[y]]; return w[x]<w[y]?x:y; } } int op[100010],ql1[100010],qr1[100010],ql2[100010],qr2[100010]; int n,m; int s[200010],s1[100010],s2[100010],s3[100010]; int pre[200010],suf[200010]; int nl,nr; void addr() { nr++; int x=tree::find(pre[nr],nr-nl+1); tree::add(x,1); } void addl() { nl--; int x=tree::find(suf[nl],nr-nl+1); tree::add(x,1); } void queryl(int l1,int r1,int l2,int r2) { int x=tree::find(pre[r1],r1-l1+1); int y=tree::find(pre[r2],r2-l2+1); int lca=tree::getlca(x,y); ll ans=tree::query(x,y); int len=tree::v[lca]; if(r1-l1+1!=len&&r2-l2+1!=len&&s[r1-len]==s[r2-len]) ans-=sgt::query(rt,tree::w[lca],tree::w[lca]); printf("%lld\n",ans); } void queryr(int l1,int r1,int l2,int r2) { int x=tree::find(suf[l1],r1-l1+1); int y=tree::find(suf[l2],r2-l2+1); int lca=tree::getlca(x,y); ll ans=tree::query(x,y); int len=tree::v[lca]; if(r1-l1+1!=len&&r2-l2+1!=len&&s[l1+len]==s[l2+len]) ans-=sgt::query(rt,tree::w[lca],tree::w[lca]); printf("%lld\n",ans); } int main() { #ifndef ONLINE_JUDGE freopen("b.in","r",stdin); freopen("b.out","w",stdout); #endif char str[10]; scanf("%d%d",&n,&m); int n1=0,n2=0; int i; for(i=1;i<=n;i++) { scanf("%d",&s3[i]); s3[i]++; } for(i=1;i<=m;i++) { scanf("%s",str); if(str[0]=='a') { scanf("%d",&ql1[i]); ql1[i]++; if(str[3]=='l') { op[i]=1; s1[++n1]=ql1[i]; } else { op[i]=2; s2[++n2]=ql1[i]; } } else { scanf("%d%d%d%d",&ql1[i],&qr1[i],&ql2[i],&qr2[i]); if(str[5]=='l') op[i]=3; else op[i]=4; } } int num=0; for(i=m;i>=1;i--) { if(op[i]==1) num++; else if(op[i]>=2) { ql1[i]+=num; qr1[i]+=num; ql2[i]+=num; qr2[i]+=num; } } int len=0; for(i=n1;i>=1;i--) s[++len]=s1[i]; for(i=1;i<=n;i++) s[++len]=s3[i]; for(i=1;i<=n2;i++) s[++len]=s2[i]; pam::init(); for(i=1;i<=len;i++) { pam::insert(s[i]); pre[i]=pam::last; } pam::rebuild(); for(i=len;i>=1;i--) { pam::insert2(s[i]); suf[i]=pam::last; } for(i=2;i<=pam::p;i++) { tree::addedge(pam::fail[i],i); tree::v[i]=pam::len[i]; } tree::dfs1(1,0,1); tree::dfs2(1,1); sgt::build(rt,1,pam::p,tree::e); nl=n1+1; nr=n1; for(i=1;i<=n;i++) addr(); for(i=1;i<=m;i++) if(op[i]==1) addl(); else if(op[i]==2) addr(); else if(op[i]==3) queryl(ql1[i],qr1[i],ql2[i],qr2[i]); else queryr(ql1[i],qr1[i],ql2[i],qr2[i]); return 0; }

【XSY2715】回文串 樹鏈剖分 回文自動機