NOI2018 你的名字 後綴自動機_線段樹合並_可持久化
阿新 • • 發佈:2019-02-15
bre int 線段樹合並 turn struct ace pen 線段樹 計數器
相當復雜的一道題,同樣也相當優美。
考察的知識點很多:
權值線段樹的可持久化合並,後綴自動機,後綴樹... 考慮 $68pts$ $l=1,r=|s|$的數據:
這部分相對好做一些,不過思維難度對我來說已經不小。但是一旦解出這一部分之後離滿分算法就不遠了。
設 ION2017 為 $S$,ION2018 為 $T$。
我們想求 $T$ 中出現的子串且在 $S$ 中未出現的種類(即重復的只算一個)。
直接求未出現的種類似乎有些苦難,我們這麽考慮:先求出 $T$ 中的所有種類,再減掉與 $S$ 重合的部分,這樣似乎能好做一些。
求 $T$ 的所有種類很很好做:對 $T$ 構建後綴自動機,種類數即為 $\sum_{i=1}^{tot} dis[i]-dis[f[i]]$(SDOI生成魔咒)
考慮如何求取相同部分。
我們可以將 $T$ 串在 $S$ 的後綴自動機上跑一遍,每新加入一個字符就沿著轉移邊和適配邊走。成功轉移,就讓記錄匹配長度的計數器++,否則就讓計數器等於失配節點的最長長度(這裏一定是最長)。 這樣,對於 $T$ 中的每個下標,我們能求出它的後綴再 $S$ 中以子串出現的最長長度。將 $T$ 中下標為 $i$ 的最長匹配後綴設為 $mx[i]$。
是不是認為答案就是類似於 $\sum_{i=1}^{strlen(T)} i-mx[i]$ ?
確實是類似的。為了使統計不出現重復,我們可以再 $T$ 的後綴自動機上進行統計(因為後綴自動機上每個節點表示的字符串集合都是互不相同的)
考察的知識點很多:
權值線段樹的可持久化合並,後綴自動機,後綴樹... 考慮 $68pts$ $l=1,r=|s|$的數據:
這部分相對好做一些,不過思維難度對我來說已經不小。但是一旦解出這一部分之後離滿分算法就不遠了。
設 ION2017 為 $S$,ION2018 為 $T$。
我們想求 $T$ 中出現的子串且在 $S$ 中未出現的種類(即重復的只算一個)。
直接求未出現的種類似乎有些苦難,我們這麽考慮:先求出 $T$ 中的所有種類,再減掉與 $S$ 重合的部分,這樣似乎能好做一些。
求 $T$ 的所有種類很很好做:對 $T$ 構建後綴自動機,種類數即為 $\sum_{i=1}^{tot} dis[i]-dis[f[i]]$(SDOI生成魔咒)
考慮如何求取相同部分。
我們可以將 $T$ 串在 $S$ 的後綴自動機上跑一遍,每新加入一個字符就沿著轉移邊和適配邊走。成功轉移,就讓記錄匹配長度的計數器++,否則就讓計數器等於失配節點的最長長度(這裏一定是最長)。 這樣,對於 $T$ 中的每個下標,我們能求出它的後綴再 $S$ 中以子串出現的最長長度。將 $T$ 中下標為 $i$ 的最長匹配後綴設為 $mx[i]$。
是不是認為答案就是類似於 $\sum_{i=1}^{strlen(T)} i-mx[i]$ ?
確實是類似的。為了使統計不出現重復,我們可以再 $T$ 的後綴自動機上進行統計(因為後綴自動機上每個節點表示的字符串集合都是互不相同的)
Code:
#include <cstdio> #include <algorithm> #include <cstring> #define maxn 5000000 #define N 30 #define ll long long #define setIO(s) freopen(s".in","r",stdin) ,freopen(s".out","w",stdout) using namespace std; char str[maxn],ss[maxn]; int str_len,ss_len; int nodes; int C[maxn],rk[maxn]; int rt[maxn]; int pos[maxn],mx[maxn]; struct Segment_Tree{ int l,r,sumv; }node[maxn<<2]; int newnode(){ return ++nodes; } void modify(int p,int l,int r,int &o){ if(!o) o=newnode(); ++node[o].sumv; if(l==r) return; int mid=(l+r)>>1; if(p<=mid) modify(p,l,mid,node[o].l); else modify(p,mid+1,r,node[o].r); } int merge(int x,int y){ if(!x||!y) return x+y; int o=newnode(); node[o].sumv=node[x].sumv+node[y].sumv; node[o].l=merge(node[x].l,node[y].l); node[o].r=merge(node[x].r,node[y].r); return o; } int query(int l,int r,int L,int R,int o){ if(l>r||r<L||l>R) return 0; if(l>=L&&r<=R) return node[o].sumv; int mid=(l+r)>>1,res=0; if(node[o].l) res+=query(l,mid,L,R,node[o].l); if(node[o].r) res+=query(mid+1,r,L,R,node[o].r); return res; } struct SAM{ int last,tot,dis[maxn],ch[maxn][N],f[maxn],pos[maxn]; void init() { last=++tot; } void ins(int c,int y,int z,int rot){ int p=last,np=++tot; last=np; dis[np]=dis[p]+1; pos[np] = z; while(p&&!ch[p][c])ch[p][c]=np,p=f[p]; if(!p) f[np]=rot; else{ int q=ch[p][c],nq; if(dis[q]==dis[p]+1) f[np]=q; else{ nq=++tot; dis[nq]=dis[p]+1; pos[nq] = pos[q]; memcpy(ch[nq],ch[q],sizeof(ch[q])); f[nq]=f[q],f[q]=f[np]=nq; while(p&&ch[p][c]==q) ch[p][c]=nq,p=f[p]; } } if(y) modify(y,1,str_len,rt[np]); } void build_S1(){ for(int i=1;i<=tot;++i) C[dis[i]]++; for(int i=1;i<=tot;++i) C[i]+=C[i-1]; for(int i=1;i<=tot;++i) rk[C[dis[i]]--]=i; for(int i=tot;i>=1;--i) { int p=rk[i]; rt[f[p]] = merge(rt[f[p]],rt[p]); } } }S1,S2; int main(){ //setIO("input"); scanf("%s",str+1),str_len=strlen(str+1),S1.init(); for(int i=1;i<=str_len;++i) S1.ins(str[i]-‘a‘,i,0,1); S1.build_S1(); int queries,l,r,st,ed; scanf("%d",&queries); while(queries--) { S2.init(),st=S2.tot,scanf("%s%d%d",ss+1,&l,&r),ss_len=strlen(ss+1); for(int i=1;i<=ss_len;++i) S2.ins(ss[i]-‘a‘,0,i,st); ed=S2.tot; int cnt=0,p=1; long long ans = 0; for(int i=1;i<=ss_len;++i) { int c=ss[i]-‘a‘; while(1){ if(S1.ch[p][c] && query(1,str_len,l+cnt,r,rt[S1.ch[p][c]])) { ++cnt,p=S1.ch[p][c]; break; } else { if(!cnt) {p=1;break; } --cnt; if(cnt==S1.dis[S1.f[p]]) p=S1.f[p]; } } mx[i]=cnt; } for(int i=st;i<=ed;++i) ans+=max(0,S2.dis[i]-max(mx[S2.pos[i]],S2.dis[S2.f[i]])); printf("%lld\n",ans); } return 0; }
NOI2018 你的名字 後綴自動機_線段樹合並_可持久化