BZOJ4556 [Tjoi2016&Heoi2016]字符串 SA ST表 二分答案 主席树
原文链接https://www.cnblogs.com/zhouzhendong/p/BZOJ4556.html
题目传送门 - BZOJ4556
题意
给定一个长度为 $n$ 的字符串 $s$ 。
有 $m$ 次询问,每次询问的格式为 $a,b,c,d$ ,问 $s[c\cdots d]$ 与 $\underline {s[a\cdots b]}$ 的所有子串 的 LCP 的最大值是多少。
$n,m\leq 10^5$
题解
首先,我们对于每一个询问考虑二分答案。显然答案的上下界分别是 $0$ 和 $d-c+1$ 。
那么如何判断一个答案是否合法?
假设答案为 $x$ ,那么我们要判断是否有满足条件的点。
条件是:在 $[a,b-x+1]$ 为左端点的 $s$ 的后缀中,是否存在一个后缀,满足它与以 $\underline{c}$ 为左端点的后缀的 LCP 不小于 $x$ 。
于是我们考虑对后缀排序,即我们跑一跑 SA 。
那么,满足条件最后一部分的后缀的 $rank$ 必然是连续的一段。我们可以通过倍增和预处理 ST 表来找到这个左右界 $[L,R]$。
于是,我们就将这个判断答案是否合法的问题转化成了一个不带修改的二维数点问题。其中,一个维度是 $[a,b-x+1]$ 另一个是 $[L,R]$。
那么我们只需要写个主席树预处理一下就可以了。
时间复杂度 $O(n\log ^2 n)$ ,我第一发交是卡着时限过去的,后来把常数写的优美了一点,跑的不是很快,$16^+s$ 。
代码
#include <bits/stdc++.h> #define y1 _83f9 using namespace std; const int N=100005; int read(){ int x=0; char ch=getchar(); while (!isdigit(ch)) ch=getchar(); while (isdigit(ch)) x=(x<<1)+(x<<3)+ch-48,ch=getchar(); return x; } int n,m; int rank[N],tax[N],SA[N],tmp[N],height[N]; int ST[N][17],Log[N]; char s[N]; void Sort(int n,int m){ for (int i=1;i<=m;i++) tax[i]=0; for (int i=1;i<=n;i++) tax[rank[i]]++; for (int i=1;i<=m;i++) tax[i]+=tax[i-1]; for (int i=n;i>=1;i--) SA[tax[rank[tmp[i]]]--]=tmp[i]; } bool cmp(int rk[],int x,int y,int w){ return rk[x]==rk[y]&&rk[x+w]==rk[y+w]; } void Suffix_Array(char s[],int n){ memset(SA,0,sizeof SA); memset(tax,0,sizeof tax); memset(tmp,0,sizeof tmp); memset(rank,0,sizeof rank); memset(height,0,sizeof height); int m=200; for (int i=1;i<=n;i++) rank[i]=s[i],tmp[i]=i; Sort(n,m); for (int w=1,p=0;p<n;w<<=1,m=p){ p=0; for (int i=n-w+1;i<=n;i++) tmp[++p]=i; for (int i=1;i<=n;i++) if (SA[i]>w) tmp[++p]=SA[i]-w; Sort(n,m); swap(tmp,rank); rank[SA[1]]=p=1; for (int i=2;i<=n;i++) rank[SA[i]]=cmp(tmp,SA[i],SA[i-1],w)?p:++p; } for (int i=1,j,k=0;i<=n;height[rank[i++]]=k) for (k=max(k-1,0),j=SA[rank[i]-1];s[i+k]==s[j+k];k++); } void Get_ST(int n){ Log[1]=0; for (int i=2;i<=n;i++){ Log[i]=Log[i>>1]+1; ST[i][0]=height[i]; for (int j=1;j<17;j++){ ST[i][j]=ST[i][j-1]; if (i-(1<<(j-1))>0&&ST[i-(1<<(j-1))][j-1]<ST[i][j]) ST[i][j]=ST[i-(1<<(j-1))][j-1]; } } } int Query(int L,int R){ int d=Log[R-L+1]; return min(ST[L+(1<<d)-1][d],ST[R][d]); } int LCP(int x,int y){ x=rank[x],y=rank[y]; return Query(min(x,y)+1,max(x,y)); } // Start to build President Tree const int S=N*20*2; int root[N],ls[S],rs[S],sum[S],tot=0; int newnode(){ tot++; ls[tot]=rs[tot]=sum[tot]=0; return tot; } void build(int &rt,int L,int R){ rt=newnode(); if (L==R) return; int mid=(L+R)>>1; build(ls[rt],L,mid); build(rs[rt],mid+1,R); } void update(int prt,int &rt,int L,int R,int x,int v){ if (!rt||rt==prt) rt=newnode(),sum[rt]=sum[prt]; sum[rt]+=v; if (L==R) return; if (!ls[rt]) ls[rt]=ls[prt]; if (!rs[rt]) rs[rt]=rs[prt]; int mid=(L+R)>>1; if (x<=mid) update(ls[prt],ls[rt],L,mid,x,v); else update(rs[prt],rs[rt],mid+1,R,x,v); } int query(int prt,int rt,int L,int R,int xL,int xR){ if (L>xR||R<xL) return 0; if (xL<=L&&R<=xR) return sum[rt]-sum[prt]; int mid=(L+R)>>1; return query(ls[prt],ls[rt],L,mid,xL,xR) +query(rs[prt],rs[rt],mid+1,R,xL,xR); } int Query(int x1,int x2,int y1,int y2){ return query(root[x1-1],root[x2],1,n,y1,y2); } bool check(int k,int x,int a,int b){ if (k==0) return 1; if (b-a+1<k) return 0; int L=x,R=x; for (int i=16;i>=0;i--){ if (L-(1<<i)>=1&&Query(L-(1<<i)+1,x)>=k) L-=1<<i; if (R+(1<<i)<=n&&Query(x+1,R+(1<<i))>=k) R+=1<<i; } return Query(a,b-k+1,L,R); } int main(){ n=read(),m=read(); scanf("%s",s+1); Suffix_Array(s,n); Get_ST(n); build(root[0],1,n); for (int i=1;i<=n;i++) update(root[i-1],root[i],1,n,rank[i],1); while (m--){ int L1=read(),R1=read(),L2=read(),R2=read(); int L=0,R=R2-L2+1,mid,ans=0; while (L<=R){ int mid=(L+R)>>1; if (check(mid,rank[L2],L1,R1)) L=mid+1,ans=mid; else R=mid-1; } printf("%d\n",ans); } return 0; }