1. 程式人生 > >UOJ #395. 【NOI2018】你的名字 字尾自動機

UOJ #395. 【NOI2018】你的名字 字尾自動機

題意

給一個串S,每次詢問給出一個串T和區間[l,r],問T中有多少個不同的子串滿足其不是S[l…r]的子串。
S,T5105,q105,T106|S|,|T|\le5*10^5,q\le10^5,\sum |T|\le10^6

分析

在noi考場上打了個又臭又長的做法,還只有68分。正解其實並不算難。
先補集轉化一下,變成求T有多少個子串是S[l…r]的子串。
先對S建sam,然後每次對T也建一個sam。先對T的每一個字首,求出一個最大的L使得該字首的L字尾是S[l…r]的子串,那麼T的sam上的某一個節點的貢獻就很容易求了。
由於要對S的sam建可持久化線段樹來維護right集,所以時間複雜度是O

((S+T)logn)O((|S|+\sum |T|)logn)

程式碼

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>

typedef long long LL;

const int N=500005;

int n,m,suf[N],b[N],c[N*2],tot;
char str[N];
struct tree{int l,r;}t[N*40];

void
ins(int &d,int l,int r,int x) { if (!d) d=++tot; if (l==r) return; int mid=(l+r)/2; if (x<=mid) ins(t[d].l,l,mid,x); else ins(t[d].r,mid+1,r,x); } int query(int d,int l,int r,int x) { if (!d) return 0; if (l==r) return l; int mid=(l+r)/2,ans; if (x>mid&&(ans=query(t[d].r,mid+
1,r,x))) return ans; else return query(t[d].l,l,mid,x); } int merge(int x,int y) { if (!x||!y) return x^y; int z=++tot; t[z].l=merge(t[x].l,t[y].l); t[z].r=merge(t[x].r,t[y].r); return z; } struct Sam1 { int sz,last,ch[N*2][26],fa[N*2],mx[N*2],root[N*2]; void extend(int x,int id) { int p,q,np,nq; p=last;last=np=++sz;mx[sz]=mx[p]+1; for (;p&&!ch[p][x];p=fa[p]) ch[p][x]=np; if (!p) fa[np]=1; else { q=ch[p][x]; if (mx[q]==mx[p]+1) fa[np]=q; else { nq=++sz;mx[nq]=mx[p]+1; memcpy(ch[nq],ch[q],sizeof(ch[q])); fa[nq]=fa[q];fa[q]=fa[np]=nq; for (;ch[p][x]==q;p=fa[p]) ch[p][x]=nq; } } ins(root[np],1,n,id); } void build() { memset(b,0,sizeof(b)); for (int i=1;i<=sz;i++) b[mx[i]]++; for (int i=1;i<=n;i++) b[i]+=b[i-1]; for (int i=1;i<=sz;i++) c[b[mx[i]]--]=i; for (int i=sz;i>1;i--) root[fa[c[i]]]=merge(root[c[i]],root[fa[c[i]]]); } }Sam1; struct Sam2 { int sz,last,ch[N*2][26],fa[N*2],mx[N*2],rig[N*2]; void extend(int x,int id) { int p,q,np,nq; p=last;last=np=++sz;mx[np]=mx[p]+1; memset(ch[np],0,sizeof(ch[np])); for (;p&&!ch[p][x];p=fa[p]) ch[p][x]=np; if (!p) fa[np]=1; else { q=ch[p][x]; if (mx[q]==mx[p]+1) fa[np]=q; else { nq=++sz;mx[nq]=mx[p]+1; memcpy(ch[nq],ch[q],sizeof(ch[q])); fa[nq]=fa[q];fa[q]=fa[np]=nq; for (;ch[p][x]==q;p=fa[p]) ch[p][x]=nq; } } rig[np]=id; } void build() { for (int i=1;i<=m;i++) b[i]=0; for (int i=1;i<=sz;i++) b[mx[i]]++; for (int i=1;i<=m;i++) b[i]+=b[i-1]; for (int i=1;i<=sz;i++) c[b[mx[i]]--]=i; for (int i=sz;i>1;i--) rig[fa[c[i]]]=rig[c[i]]; } }Sam2; int main() { scanf("%s",str+1); n=strlen(str+1); Sam1.last=Sam1.sz=1; for (int i=1;i<=n;i++) Sam1.extend(str[i]-'a',i); Sam1.build(); int q;scanf("%d",&q); while (q--) { int l,r; scanf("%s%d%d",str+1,&l,&r); m=strlen(str+1); int now=1,len=0; Sam2.sz=Sam2.last=1; memset(Sam2.ch[1],0,sizeof(Sam2.ch[1])); for (int i=1;i<=m;i++) { int c=str[i]-'a'; Sam2.extend(c,i); while (now&&!Sam1.ch[now][c]) now=Sam1.fa[now],len=Sam1.mx[now]; if (!now) now=1; else now=Sam1.ch[now][c],len++; while (now) { int x=query(Sam1.root[now],1,n,r); if (!x||x-l+1<=Sam1.mx[Sam1.fa[now]]) now=Sam1.fa[now],len=Sam1.mx[now]; else {len=std::min(len,x-l+1);break;} } suf[i]=len; } Sam2.build(); LL ans=0; for (int i=2;i<=Sam2.sz;i++) { ans+=Sam2.mx[i]-Sam2.mx[Sam2.fa[i]]; int x=Sam2.rig[i]; ans-=std::max(0,std::min(suf[x],Sam2.mx[i])-Sam2.mx[Sam2.fa[i]]); } printf("%lld\n",ans); } return 0; }