P2336 [SCOI2012] 喵星球上的点名 题解
P2336 [SCOI2012] 喵星球上的点名
考虑后缀数组的常见套路:把所有串中间用奇怪字符拼在一起,记录每个位置上的字符是哪个文本串的,求出 \(\rm sa\) 和 \(\rm height\)。
看到子串,显然转化为后缀数组上的 LCP 问题。又由那条经典性质:\(\operatorname{LCP}(\text{sa}(i),\text{sa}(j))=\min_{i<k\le j}\{\text{height}(k)\}\),可以得到一个重要结论:找到一个后缀在 height 数组上的位置后,向两边走 RMQ 一定不会变得更大,也就是说满足条件的后缀位于一个区间内。
于是我们考虑对于每个模式串,二分找到其向左和向右最远能够到达哪个后缀,使得它和这个后缀的 LCP 长度不小于这个模式串的长度。于是询问被我们转化成了一个个区间,每个询问实际上包含两个问题:
- 每个区间有多少种数;
- 每种数被多少区间包括。
又是 \(50000\) 的数据范围又允许离线,那显然莫队了。第一问就是莫队的板题,不再赘述;第二问的处理方法比较特殊,因为在计算它的过程中我们必须保证单次 \(O(1)\)。这里我们采用差分的思想,当这种数第一次出现时,给它的答案加上最大可能出现次数,也就是剩余数的数量;在它消失时,再减去剩余数的数量,就统计出了答案。
代码实现方面,难点主要在于二分的准确性。因为每个模式串有可能连一个文本串也匹配不上,但它又不能匹配上自己,这就要求给不合法区间的 \(l,r\) 设置为 \(l>r\) 来体现其不合法,就不会算入多余答案。
constexpr int MAXN=4e5+50;
int N,M,n,s[MAXN],bg[MAXN],len[MAXN];
int sa[MAXN],rk[MAXN],rk2[MAXN],id[MAXN],cnt[MAXN];
int h[MAXN];
int col[MAXN],f[MAXN][21];
int t,sm,ans[MAXN],res[MAXN];
struct Ask{
int l,r,id;
bool operator<(const Ask&x)const{
return l/t!=x.l/t?l<x.l:l/t&1?r<x.r:r>x.r;
}
}q[MAXN];
void getsa(int m){
for(int i=1;i<=n;i++) cnt[rk[i]=s[i]]++;
for(int i=1;i<=m;i++) cnt[i]+=cnt[i-1];
for(int i=n;i;i--) sa[cnt[rk[i]]--]=i;
for(int w=1,p,cur;;w<<=1,m=p){
cur=0;
for(int i=n-w+1;i<=n;i++) id[++cur]=i;
for(int i=1;i<=n;i++) if(sa[i]>w) id[++cur]=sa[i]-w;
memset(cnt,0,(m+1)<<2);
for(int i=1;i<=n;i++) cnt[rk[i]]++;
for(int i=1;i<=m;i++) cnt[i]+=cnt[i-1];
for(int i=n;i;i--) sa[cnt[rk[id[i]]]--]=id[i];
p=0;
memcpy(rk2,rk,(n+1)<<2);
for(int i=1;i<=n;i++)
rk[sa[i]]=rk2[sa[i]]==rk2[sa[i-1]]&&rk2[sa[i]+w]==rk2[sa[i-1]+w]?p:++p;
if(p==n) break;
}
}
void geth(){
for(int i=1,k=0;i<=n;i++){
if(!rk[i]) continue;
if(k) k--;
while(s[i+k]==s[sa[rk[i]-1]+k]) k++;
h[rk[i]]=k;
}
}
void ST(){
for(int i=1;i<=n;i++) f[i][0]=h[i];
for(int j=1;j<=20;j++)
for(int i=1;i+(1<<j)-1<=n;i++)
f[i][j]=min(f[i][j-1],f[i+(1<<(j-1))][j-1]);
}
int amn(int l,int r){
int s=__lg(r-l+1);
return min(f[l][s],f[r-(1<<s)+1][s]);
}
int bs1(int x,int val){
int l=1,r=x,ans=x;
while(l<=r){
int mid=(l+r)>>1;
if(amn(mid,x)>=val) ans=mid-1,r=mid-1;
else l=mid+1;
}
return ans;
}
int bs2(int x,int val){
int l=x+1,r=n,ans=x-1;
while(l<=r){
int mid=(l+r)>>1;
if(amn(x+1,mid)>=val) ans=mid,l=mid+1;
else r=mid-1;
}
return ans;
}
void add(int x,int id){
if(++cnt[col[sa[x]]]==1&&col[sa[x]]){
sm++;
res[col[sa[x]]]+=M-id+1;
}
}
void del(int x,int id){
if(cnt[col[sa[x]]]--==1&&col[sa[x]]){
sm--;
res[col[sa[x]]]-=M-id+1;
}
}
int main(){
N=read(),M=read();
for(int i=1,l;i<=N;i++){
l=read();
for(int j=1;j<=l;j++){
s[++n]=read();
col[n]=i;
}
s[++n]=2e4+1;
l=read();
for(int j=1;j<=l;j++){
s[++n]=read();
col[n]=i;
}
s[++n]=2e4+1;
}
for(int i=1;i<=M;i++){
len[i]=read();
bg[i]=n+1;
for(int j=1;j<=len[i];j++) s[++n]=read();
if(i<M) s[++n]=2e4+1;
}
getsa(2e4+1),geth(),ST();
t=sqrt(n);
for(int i=1;i<=M;i++) q[i]={bs1(rk[bg[i]],len[i]),bs2(rk[bg[i]],len[i]),i};
sort(q+1,q+M+1);
memset(cnt,0,(N+1)<<2);
for(int i=1,l=1,r=0;i<=M;i++){
while(l>q[i].l) add(--l,i);
while(r<q[i].r) add(++r,i);
while(l<q[i].l) del(l++,i);
while(r>q[i].r) del(r--,i);
ans[q[i].id]=sm;
}
for(int i=1;i<=M;i++) write(ans[i]);
for(int i=1;i<=N;i++) write(res[i],' ');
return putchar('\n'),fw,0;
}
即得易见平凡,仿照上例显然。留作习题答案略,读者自证不难。
反之亦然同理,推论自然成立。略去过程 $\rm QED$,由上可知证毕。