1. 程式人生 > >【XSY1551】往事 廣義後綴數組 線段樹合並

【XSY1551】往事 廣義後綴數組 線段樹合並

關鍵字排序 字符串 stdin time utili 題目 信息 break 後綴數組

題目大意

  給你一顆trie樹,令\(s_i\)為點\(i\)到根的路徑上的字符組成的字符串。求\(max_{u\neq v}(LCP(s_u,s_v)+LCS(s_u,s_v))\)

  \(LCP=\)最長公共前綴,\(LCS=\)最長公共後綴

  \(1\leq n\leq 200000\),字符集為\(\{0\ldots 300\}\)

題解

  我們先看看這個\(LCP(s_u,s_v)\)怎麽求

  廣義後綴自動機不行。廣義後綴樹可能可以,但我不會。廣義後綴數組可以。然後我就開始手推廣義後綴數組

  廣義後綴數組:和後綴數組類似,求出\(s_i\)的排名以及\(LCP(s_{sa_{}i-1},s_{sa_i})\)

  實現也和後綴數組類似,倍增,把兩段\(2^{i-1}\)的信息合並成\(2^i\)的信息。另外還要保存\(s_i\)的長度為\(2^j\)的前綴在所有字符串的長度為\(2^j\)的前綴中的排名。

  求完\(sa_i\)\(rk_i\)後,我們用二分+哈希求\(LCP(s_{sa_{i-1}},s_{sa_i})\)。上面保存下來的所有長度為\(2^j\)的前綴的排名可以當哈希值來用(考場上我寫了哈希)。然後用st表來維護區間最小值。現在我們可以\(O(1)\)求出\(LCP(s_u,s_v)\)

  考慮以\(x\)為根的子樹,若\(u,v\)\(x\)的不同子樹內,則\(LCS(s_u,s_v)=d_x-1\)

。這裏\(d_x\)為點\(x\)的深度,根的深度為1。

  因為\(LCS(s_{sa_{i-1}},s_{sa_i})\geq LCS(s_{sa_x},s_{sa_y})~(x\leq i-1<i\leq y)\),所以我們只用求出以\(rk_i\)為關鍵字排序後相鄰兩個點的\(LCP\)。這個可以用線段樹維護。然後線段樹合並即可。

  時間復雜度:\(O(n\log n)\)

代碼

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cstdlib>
#include<ctime>
#include<utility> using namespace std; typedef long long ll; typedef pair<int,int> pii; struct list { int v[200010]; int t[200010]; int w[200010]; int h[200010]; int n; list() { n=0; memset(h,0,sizeof h); } void clear() { n=0; memset(h,0,sizeof h); } void add(int x,int y,int z) { n++; v[n]=y; t[n]=h[x]; w[n]=z; h[x]=n; } }; list l,l2; int f[200010][20]; ll hs[200010][20]; int d[200010]; int e[200010]; int sa[200010]; int rk[200010]; int sx[200010]; int sy[200010]; int b[200010]; ll mod=1000000007; ll pw[200010]; int ht[200010]; int st[200010][20]; int lo[200010]; int rt[200010]; int n; int getmin(int x,int y) { int t=lo[y-x+1]; return min(st[x][t],st[y-(1<<t)+1][t]); } int query(int x,int y) { // x=rk[x]; // y=rk[y]; if(x==y) return 0x3fffffff; if(x>y) swap(x,y); return getmin(x+1,y); } namespace seg { struct p { int s,first,last,sz; p() { s=first=last=sz=0; } }; int cnt; int ls[4000010]; int rs[4000010]; p s[4000010]; void init() { memset(ls,0,sizeof ls); memset(rs,0,sizeof rs); cnt=0; } p mt(p a,p b) { if(!a.sz) return b; if(!b.sz) return a; p c; c.sz=a.sz+b.sz; c.first=a.first; c.last=b.last; c.s=max(max(a.s,b.s),query(a.last,b.first)); return c; } int insert(int p,int x,int l,int r) { if(!p) p=++cnt; if(l==r) { s[p].sz=1; s[p].first=s[p].last=x; return p; } int mid=(l+r)>>1; if(x<=mid) ls[p]=insert(ls[p],x,l,mid); else rs[p]=insert(rs[p],x,mid+1,r); s[p]=mt(s[ls[p]],s[rs[p]]); return p; } int merge(int x,int y) { if(!x||!y) return x+y; ls[x]=merge(ls[x],ls[y]); rs[x]=merge(rs[x],rs[y]); s[x]=mt(s[ls[x]],s[rs[x]]); return x; } }; int ans=0; void solve(int x) { rt[x]=seg::insert(rt[x],rk[x],1,n); int i; for(i=l.h[x];i;i=l.t[i]) { solve(l.v[i]); rt[x]=seg::merge(rt[x],rt[l.v[i]]); } if(seg::s[rt[x]].sz>1) ans=max(ans,d[x]-1+seg::s[rt[x]].s); } int main() { memset(rt,0,sizeof rt); seg::init(); int i,j,x,y; pw[0]=311; for(i=1;i<=50;i++) pw[i]=pw[i-1]*pw[i-1]%mod; freopen("recollection.in","r",stdin); freopen("recollection.out","w",stdout); memset(f,0,sizeof f); scanf("%d",&n); d[1]=1; e[1]=310; hs[1][0]=310; for(i=2;i<=n;i++) { scanf("%d%d",&x,&y); l.add(x,i,y); f[i][0]=x; d[i]=d[x]+1; e[i]=y+1; hs[i][0]=e[i]; for(j=1;j<=18;j++) { f[i][j]=f[f[i][j-1]][j-1]; hs[i][j]=(hs[f[i][j-1]][j-1]*pw[j-1]+hs[i][j-1])%mod; } } int sz=310,k,o; for(i=1;i<=sz;i++) b[i]=0; for(i=1;i<=n;i++) b[sx[i]=e[i]]++; for(i=2;i<=sz;i++) b[i]+=b[i-1]; for(i=n;i>=1;i--) sa[b[sx[i]]--]=i; for(j=1;(1<<j)<=n;j++) { l2.clear(); k=0; for(i=1;i<=n;i++) if(d[sa[i]]>(1<<(j-1))) l2.add(f[sa[i]][j-1],sa[i],0); for(i=1;i<=n;i++) for(o=l2.h[sa[i]];o;o=l2.t[o]) sy[++k]=l2.v[o]; for(i=1;i<=n;i++) if(d[i]<=(1<<(j-1))) sy[++k]=i; for(i=1;i<=sz;i++) b[i]=0; for(i=1;i<=n;i++) b[sx[sy[i]]]++; for(i=2;i<=sz;i++) b[i]+=b[i-1]; for(i=n;i>=1;i--) sa[b[sx[sy[i]]]--]=sy[i]; k=0; swap(sx,sy); for(i=1;i<=n;i++) if(i!=1&&sy[sa[i]]==sy[sa[i-1]]&&((d[sa[i]]<=(1<<(j-1))&&d[sa[i-1]]<=(1<<(j-1)))||(d[sa[i]]>(1<<(j-1))&&d[sa[i-1]]>(1<<(j-1))&&sy[f[sa[i]][j-1]]==sy[f[sa[i-1]][j-1]]))) sx[sa[i]]=k; else sx[sa[i]]=++k; if(k>=n) break; sz=k; } for(i=1;i<=n;i++) rk[sa[i]]=i; ht[0]=0; for(i=2;i<=n;i++) { x=sa[i-1]; y=sa[i]; int now=0; for(j=18;j>=0;j--) if(d[x]-1>=(1<<j)&&d[y]-1>=(1<<j)&&hs[x][j]==hs[y][j]) { now+=(1<<j); x=f[x][j]; y=f[y][j]; } ht[i]=now; } for(i=1;i<=n;i++) st[i][0]=ht[i]; for(j=1;j<=18;j++) for(i=1;i+(1<<j)-1<=n;i++) st[i][j]=min(st[i][j-1],st[i+(1<<(j-1))][j-1]); lo[1]=0; for(i=2;i<=n;i++) lo[i]=lo[i/2]+1; solve(1); printf("%d\n",ans); return 0; }

【XSY1551】往事 廣義後綴數組 線段樹合並