[BZOJ3277] 串

Description

现在给定你n个字符串,询问每个字符串有多少子串(不包括空串)是所有n个字符串中至少k个字符串的子串(注意包括本身)。

Solution

首先将所有串连接起来,预处理出后缀数组和高度数组。

显然直接主席树可以很容易做到 \(O(n \log^2 n)\) 。对于每一个后缀的位置,二分一个 LCP 长度,找到这个 LCP 长度对应的区间,检查这个区间是否合法来调节二分边界。

注意在这个做法里,瓶颈不在于主席树,因为主席树的功能完全可以用双指针预处理一个数组来替代。瓶颈在于,实质上使用了一个二分套二分的做法。

但我们有更好的做法。

引理:按照原始顺序,如果第 \(i\) 个后缀有 \(x\) 个前缀能被 \(k\) 个串包含,那么第 \(i+1\) 个后缀至少有 \(x-1\) 个前缀能被 \(k\) 个串包含。

那么我们先用双指针预处理 \(jmp[i]\) 代表按照后缀排序,最大的 \(j\) 使得 \([j,i]\) 这个后缀区间合法。

到第 \(i\) 个后缀的时候我们就从后缀 \(i-1\) 的答案开始向上枚举,用二分+ST表找出它左右边第一个高度比当前枚举值小的位置,判断这个区间的合法性来决定是否继续枚举,均摊时间复杂度 \(O(nlogn)\) 。

\(O(\log n)\) 解法

#include <bits/stdc++.h>
using namespace std; #define int long long
const int N = 400005; int n,m=N/2,sa[N],y[N],u[N],v[N],o[N],r[N],h[N],T,nstr,k;
int str[N],Log2[N],bel[N],buf[N],bcnt,jmp[N],mx[N],ans[N],tow[N];
char tstr[N]; struct St {
int a[N][21];
void build(int *src,int n) {
for(int i=1;i<=n;i++) a[i][0]=src[i];
for(int i=1;i<=20;i++)
for(int j=1;j<=n-(1<<i)+1;j++)
a[j][i]=min(a[j][i-1],a[j+(1<<(i-1))][i-1]);
}
int query(int l,int r) {
if(l>r) return 0;
int j=Log2[r-l+1];
return min(a[l][j],a[r-(1<<j)+1][j]);
}
} st; int lbound(int cen,int val) {
int l=1,r=cen;
while(r>l) {
int mid=(l+r)/2;
if(st.query(mid+1,cen)>=val) r=mid;
else l=mid+1;
}
return l;
} int rbound(int cen,int val) {
int l=cen+1,r=n+1;
while(r>l) {
int mid=(l+r)/2;
if(st.query(cen+1,mid)>=val) l=mid+1;
else r=mid;
}
return l-1;
} signed main(){
for(int i=1;i<=200000;i++) Log2[i]=log2(i);
scanf("%lld%lld",&nstr,&k);
for(int i=1;i<=nstr;i++) {
scanf("%s",tstr);
int len=strlen(tstr);
for(int j=0;j<len;j++) str[j+n+1]=tstr[j],bel[j+n+1]=i,tow[j+n+1]=n+len;
n+=len+1;
str[n]=127+i;
} for(int i=1;i<=n;i++) u[str[i]]++;
for(int i=1;i<=m;i++) u[i]+=u[i-1];
for(int i=n;i>=1;i--) sa[u[str[i]]--]=i;
r[sa[1]]=1;
for(int i=2;i<=n;i++) r[sa[i]]=r[sa[i-1]]+(str[sa[i]]!=str[sa[i-1]]); for(int l=1;r[sa[n]]<n;l<<=1) {
memset(u,0,sizeof u);
memset(v,0,sizeof v);
memcpy(o,r,sizeof r);
for(int i=1;i<=n;i++) u[r[i]]++, v[r[i+l]]++;
for(int i=1;i<=n;i++) u[i]+=u[i-1], v[i]+=v[i-1];
for(int i=n;i>=1;i--) y[v[r[i+l]]--]=i;
for(int i=n;i>=1;i--) sa[u[r[y[i]]]--]=y[i];
r[sa[1]]=1;
for(int i=2;i<=n;i++) r[sa[i]]=r[sa[i-1]]+((o[sa[i]]!=o[sa[i-1]])||(o[sa[i]+l]!=o[sa[i-1]+l]));
}
{
int i,j,k=0;
for(int i=1;i<=n;h[r[i++]]=k)
for(k?k--:0,j=sa[r[i]-1];str[i+k]==str[j+k];k++);
} st.build(h,n); bcnt=1;
buf[bel[sa[n]]]++;
for(int i=n,j=n;i>=1;--i) {
while(bcnt<k && j>0) {
--j;
if(buf[bel[sa[j]]]==0) ++bcnt;
buf[bel[sa[j]]]++;
}
jmp[i]=j;
if(buf[bel[sa[i]]]==1) --bcnt;
buf[bel[sa[i]]]--;
}
// for(int i=1;i<=n;i++) cout<<jmp[i]<<" "; cout<<endl;
for(int i=1;i<=n;i++) {
for(int j=max(1ll,mx[i-1]);j<=n;j++) {
int lb=lbound(r[i],j), rb=rbound(r[i],j);
//cout<<i<<" "<<r[i]<<" "<<j<<" "<<lb<<" "<<rb<<endl;
if(jmp[rb]<lb || j>tow[i]-i+1) {
mx[i]=j-1;
break;
}
}
}
//for(int i=1;i<=n;i++) cout<<mx[i]<<" ";
//cout<<endl;
for(int i=1;i<=n;i++) {
ans[bel[i]]+=mx[i];
}
for(int i=1;i<=nstr;i++) printf("%lld ",ans[i]);
}

\(O(\log^2 n)\) 解法 (TLE)

#include <bits/stdc++.h>
using namespace std; #define int long long
const int N = 400005; int n,m=N/2,sa[N],y[N],u[N],v[N],o[N],r[N],h[N],jmp[N],buf[N],bel[N],bcnt;
int nstr,k;
int str[N],ans[N],tow[N],LOG2[N];
char tstr[N]; struct St {
int a[N][21];
void build(int *src,int n) {
for(int i=1;i<=n;i++) a[i][0]=src[i];
for(int i=1;i<=20;i++)
for(int j=1;j<=n-(1<<i)+1;j++)
a[j][i]=min(a[j][i-1],a[j+(1<<(i-1))][i-1]);
}
int query(int l,int r) {
if(l>r) return 0;
int j=LOG2[r-l+1];
return min(a[l][j],a[r-(1<<j)+1][j]);
}
} st; int lbound(int cen,int val) {
int l=1,r=cen;
while(r-l) {
int mid=(l+r)/2;
if(st.query(mid+1,cen)>=val) r=mid;
else l=mid+1;
}
return l;
} int rbound(int cen,int val) {
int l=cen+1,r=n+1;
while(r-l) {
int mid=(l+r)/2;
if(st.query(cen+1,mid)>=val) l=mid+1;
else r=mid;
}
return l-1;
} signed main(){
for(int i=1;i<=200000;i++) LOG2[i]=log2(i);
scanf("%d%d",&nstr,&k);
for(int i=1;i<=nstr;i++) {
scanf("%s",tstr);
int tstrlength = strlen(tstr);
for(int j=0;j<tstrlength;j++)
str[n+j+1]=tstr[j],bel[n+j+1]=i,tow[n+j+1]=n+tstrlength;
n+=tstrlength+1;
str[n]=127+i;
} for(int i=1;i<=n;i++) u[str[i]]++;
for(int i=1;i<=m;i++) u[i]+=u[i-1];
for(int i=n;i>=1;i--) sa[u[str[i]]--]=i;
r[sa[1]]=1;
for(int i=2;i<=n;i++) r[sa[i]]=r[sa[i-1]]+(str[sa[i]]!=str[sa[i-1]]); for(int l=1;r[sa[n]]<n;l<<=1) {
memset(u,0,sizeof u);
memset(v,0,sizeof v);
memcpy(o,r,sizeof r);
for(int i=1;i<=n;i++) u[r[i]]++, v[r[i+l]]++;
for(int i=1;i<=n;i++) u[i]+=u[i-1], v[i]+=v[i-1];
for(int i=n;i>=1;i--) y[v[r[i+l]]--]=i;
for(int i=n;i>=1;i--) sa[u[r[y[i]]]--]=y[i];
r[sa[1]]=1;
for(int i=2;i<=n;i++) r[sa[i]]=r[sa[i-1]]+((o[sa[i]]!=o[sa[i-1]])||(o[sa[i]+l]!=o[sa[i-1]+l]));
}
{
int i,j,k=0;
for(int i=1;i<=n;h[r[i++]]=k)
for(k?k--:0,j=sa[r[i]-1];str[i+k]==str[j+k];k++);
}
st.build(h,n);
buf[bel[sa[n]]]=1; bcnt++;
for(int i=n,j=n;i>=1;--i) {
while(bcnt<k && j>0) {
--j;
if(buf[bel[sa[j]]]==0) bcnt++;
buf[bel[sa[j]]]++;
}
jmp[i]=j;
buf[bel[sa[i]]]--;
if(buf[bel[sa[i]]]==0) bcnt--;
} for(int i=1;i<=n;i++) {
int l=1,r=tow[sa[i]]-sa[i]+2;
while(r>l) {
int mid=(l+r)/2;
int lb=lbound(i,mid),rb=rbound(i,mid);
if(jmp[rb]>=lb) l=mid+1;
else r=mid;
}
//cout<<i<<" "<<l-1<<endl;
ans[bel[sa[i]]]+=l-1;
}
for(int i=1;i<=nstr;i++) printf("%lld ",ans[i]);
}

[BZOJ3277/BZOJ3473] 串 - 后缀数组,二分,双指针,ST表,均摊分析的更多相关文章

  1. bzoj3277 串 (后缀数组+二分答案+ST表)

    常见操作:先把所有串都连到一起,但中间加上一个特殊的符号(不能在原串中/出现过)作为分割 由于全部的子串就等于所有后缀的所有前缀,那我们对于每一个后缀,去求一个最长的前缀,来满足这个前缀在至少K个原串 ...

  2. 【BZOJ2946】[Poi2000]公共串 后缀数组+二分

    [BZOJ2946][Poi2000]公共串 Description        给出几个由小写字母构成的单词,求它们最长的公共子串的长度. 任务: l        读入单词 l        计 ...

  3. [POI2000] 公共串 - 后缀数组,二分

    [POI2000] 公共串 Description 给出几个由小写字母构成的单词,求它们最长的公共子串的长度. Solution 预处理出后缀数组和高度数组,二分答案 \(k\) ,对于每一个连续的 ...

  4. [HEOI2016] 字符串 - 后缀数组,主席树,ST表,二分

    [HEOI2016] 字符串 Description 给定一个字符串 \(S\), 有 \(m\) 个询问,每个询问给定参数 \((a,b,c,d)\) ,求 \(s[a..b]\) 的子串与 \(s ...

  5. BZOJ4199 [Noi2015]品酒大会 【后缀数组 + 单调栈 + ST表】

    题目 一年一度的"幻影阁夏日品酒大会"隆重开幕了.大会包含品尝和趣味挑战两个环节,分别向优胜者颁发"首席品 酒家"和"首席猎手"两个奖项,吸 ...

  6. BZOJ3879:SvT(后缀数组,单调栈,ST表)

    Description (我并不想告诉你题目名字是什么鬼) 有一个长度为n的仅包含小写字母的字符串S,下标范围为[1,n]. 现在有若干组询问,对于每一个询问,我们给出若干个后缀(以其在S中出现的起始 ...

  7. BZOJ_2946_[Poi2000]公共串_后缀数组+二分答案

    BZOJ_2946_[Poi2000]公共串_后缀数组+二分答案 Description          给出几个由小写字母构成的单词,求它们最长的公共子串的长度. 任务: l        读入单 ...

  8. 【bzoj4310】跳蚤 后缀数组+二分

    题目描述 很久很久以前,森林里住着一群跳蚤.一天,跳蚤国王得到了一个神秘的字符串,它想进行研究. 首先,他会把串分成不超过 k 个子串,然后对于每个子串 S,他会从S的所有子串中选择字典序最大的那一个 ...

  9. BZOJ 3230: 相似子串( RMQ + 后缀数组 + 二分 )

    二分查找求出k大串, 然后正反做后缀数组, RMQ求LCP, 时间复杂度O(NlogN+logN) -------------------------------------------------- ...

随机推荐

  1. vue富文本编辑器vue-quill-editor使用总结(包含图片上传,拖拽,放大和缩小)

    vue-quill-editor是vue很好的富文本编辑器,富文本的功能基本上都支持,样式是黑白色,简洁大方. 第一步下载 vue-quill-editor: npm i vue-quill-edit ...

  2. selenium获取缓存数据

    爬虫呢有时候数据方便有时候登入获得cookies,以及获取他存缓存中的数据 一.获取缓存中的数据其实很简单js注入就好了 localStorage_1 = driver.execute_script( ...

  3. javascript 基础整理

    js编码标准 参考 数据类型 注意事项

  4. <a>超链接标签,<button>按钮标签,实现返回跳转

    超链接: <a href=”#” onClick=”javascript :history.back(-1);”>返回上一页</a> <a href=”#” onClic ...

  5. Count the Colors ZOJ - 1610 区间颜色覆盖

    #include<stdio.h> #include<iostream> #include<string.h> #include<algorithm> ...

  6. 简单CSS的应用

    今天主要学习了一些关于CSS的内容 通过css调试了一个简单的表格 <%@ page language="java" contentType="text/html; ...

  7. Qt读写文件

    1.头文件 #include<QFile> #include<QFileDialog> #include<QDataStream> 2.写代码前工作 在ui界面拖入 ...

  8. PAT (Basic Level) Practice (中文)1023 组个最小数 (20 分) (排序)

    给定数字 0-9 各若干个.你可以以任意顺序排列这些数字,但必须全部使用.目标是使得最后得到的数尽可能小(注意 0 不能做首位).例如:给定两个 0,两个 1,三个 5,一个 8,我们得到的最小的数就 ...

  9. 暂停后保存sql server profiler的跟踪结果

  10. CQOI跳舞(网络流+二分答案)

    题面见 https://www.luogu.org/problemnew/show/P3153 题意简述:有n个男生,n个女生,每一首歌时两位男女配对,然后同一对男女只能跳一场,一个人只会与不喜欢的人 ...