[BZOJ3277/BZOJ3473] 串 - 后缀数组,二分,双指针,ST表,均摊分析
[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表,均摊分析的更多相关文章
- bzoj3277 串 (后缀数组+二分答案+ST表)
常见操作:先把所有串都连到一起,但中间加上一个特殊的符号(不能在原串中/出现过)作为分割 由于全部的子串就等于所有后缀的所有前缀,那我们对于每一个后缀,去求一个最长的前缀,来满足这个前缀在至少K个原串 ...
- 【BZOJ2946】[Poi2000]公共串 后缀数组+二分
[BZOJ2946][Poi2000]公共串 Description 给出几个由小写字母构成的单词,求它们最长的公共子串的长度. 任务: l 读入单词 l 计 ...
- [POI2000] 公共串 - 后缀数组,二分
[POI2000] 公共串 Description 给出几个由小写字母构成的单词,求它们最长的公共子串的长度. Solution 预处理出后缀数组和高度数组,二分答案 \(k\) ,对于每一个连续的 ...
- [HEOI2016] 字符串 - 后缀数组,主席树,ST表,二分
[HEOI2016] 字符串 Description 给定一个字符串 \(S\), 有 \(m\) 个询问,每个询问给定参数 \((a,b,c,d)\) ,求 \(s[a..b]\) 的子串与 \(s ...
- BZOJ4199 [Noi2015]品酒大会 【后缀数组 + 单调栈 + ST表】
题目 一年一度的"幻影阁夏日品酒大会"隆重开幕了.大会包含品尝和趣味挑战两个环节,分别向优胜者颁发"首席品 酒家"和"首席猎手"两个奖项,吸 ...
- BZOJ3879:SvT(后缀数组,单调栈,ST表)
Description (我并不想告诉你题目名字是什么鬼) 有一个长度为n的仅包含小写字母的字符串S,下标范围为[1,n]. 现在有若干组询问,对于每一个询问,我们给出若干个后缀(以其在S中出现的起始 ...
- BZOJ_2946_[Poi2000]公共串_后缀数组+二分答案
BZOJ_2946_[Poi2000]公共串_后缀数组+二分答案 Description 给出几个由小写字母构成的单词,求它们最长的公共子串的长度. 任务: l 读入单 ...
- 【bzoj4310】跳蚤 后缀数组+二分
题目描述 很久很久以前,森林里住着一群跳蚤.一天,跳蚤国王得到了一个神秘的字符串,它想进行研究. 首先,他会把串分成不超过 k 个子串,然后对于每个子串 S,他会从S的所有子串中选择字典序最大的那一个 ...
- BZOJ 3230: 相似子串( RMQ + 后缀数组 + 二分 )
二分查找求出k大串, 然后正反做后缀数组, RMQ求LCP, 时间复杂度O(NlogN+logN) -------------------------------------------------- ...
随机推荐
- Wix 快速开发安装包程序 (二)安装行为
上一小节,主要介绍了构建最小级别的安装包,这个安装包所做的事情很简单,主要是打包好一些文件,然后放到用户机器的某个位置下面. 这个小节,主要是总结安装过程的各种行为如何使用Wix编写. 一.写注册表 ...
- Element节点
Element节点对象对应网页的 HTML 元素.每一个 HTML 元素,在 DOM 树上都会转化成一个Element节点对象(以下简称元素节点).元素节点的nodeType属性都是1.Element ...
- equals和==的使用
1.equals的使用: 引用数据类型的比较:通常情况下比较的是引用数据类型下的栈中的地址,但当你重写了equals方法后就不一定了 User user1=new User("tom&quo ...
- [TJOI2008] 小偷
TJOI2008小偷 题目背景 一位著名的小偷进入了一个充满宝石的储藏室,这个储藏室是由一连串房间构成的,房间的标号从0开始,想进入第i个房间就必须从第i-1个房间进入,如图: 题目描述 上图为三个房 ...
- python中的__dict__和dir()的区别
Python下一切皆对象,每个对象都有多个属性(attribute),Python对属性有一套统一的管理方案. __dict__与dir()的区别: dir()是一个函数,返回的是list: __di ...
- defender 月考总结
今天是2019年5月28日,昨天月考了,也是C**生日.昨天考完之后,还是那种考完试的释然感.目前,已经批出来了数学.英语.物理三门学科的成绩,语文还没有批出来.应该明天就能够批出来吧.现在趁着休息, ...
- 2级搭建类201-Oracle 12cR2 单实例 ASM(OEL7.7)公开
项目文档引子系列是根据项目原型,制作的测试实验文档,目的是为了提升项目过程中的实际动手能力,打造精品文档AskScuti. 项目文档引子系列除特定项目目前不对外发布,仅作为博客记录,其他公开.如学员在 ...
- spark之RDD练习
目录 一.基础练习 练习一:翻倍列表中的数值并排序列表,并选出其中大于等于10的元素. 练习二:将字符数组里面的每一个元素先切分在压平. 练习三:求两个列表中的交集.并集.及去重后的结果 练习四:对L ...
- react-native简单使用
基本组件的使用介绍 View: Text: TextInput: Image: Button: ActivityIndicator: ScrollView:这是一个列表滚动的组件 ListView:也 ...
- jQuery---城市选择案例
城市选择案例 <!DOCTYPE html> <html> <head lang="en"> <meta charset="UT ...