[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. HTML指定页面编码

    HTML指定页面编码 <meta charset="UTF-8" />

  2. 《茶余饭后小故事》MV*、MVC、MVP、MVVM的前世今生

    今天我们讲讲历史,讲讲故事,不扯高深术语. MV*表示的意思是:M(Model逻辑层) + View(视图层) + *(中间者).上帝提出了这个逻辑与视图分离,用中间者进行连接的伟大思想,并将实现这个 ...

  3. Android实战项目——家庭记账本设计思路

    经过三周左右的Android学习,实感只有上手开发才能有所提高.在此打算做一个家庭记账APP,同时巩固一下学到的东西并且弥补漏洞. 概述 记账是自古以来人类必不可少的一件事,从古代的算盘,到手写账本, ...

  4. SP11470 TTM - To the moon[主席树标记永久化]

    SP11470 TTM - To the moon C l r d:区间 \([L,R]\) 中的数都加 d ,同时当前的时间戳加 1. Q l r:查询当前时间戳区间 \([L,R]\) 中所有数的 ...

  5. GYCTF Flaskapp[SSTI模板注入 ]

    题目复现传送门 学习链接: 找了个师傅的blog先学习一下基础的flask知识 https://www.freebuf.com/column/187845.html(从零学flask) 简单记录一下: ...

  6. C#MVC用ZXing.Net生成二维码/条形码

    开篇:zxing.net是.net平台下编解条形码和二维码的工具. 首先创建新项目 选择MVC模板  添加一个控制器  在项目引用中的引用ZXing 进行联网下载 控制器需要引用 后台控制器   pu ...

  7. Vue(三)--循环语句

    v-for: v-for 指令需要以 site in sites 形式的特殊语法, sites 是源数据数组并且 site 是数组元素迭代的别名. demo1. <!DOCTYPE html&g ...

  8. P4072 [SDOI2016]征途

    斜率优化裸题 题意大概是:求 最小的 \(m^2s^2\) =\(m^2(\frac{1}{m}\sum_{i=1}^{m}(sum_i - {\frac{\sum_{i=1}^{m}sum_i}{m ...

  9. PP: Soft-DTW: a differentiable loss function for time-series

    Problem: new loss Label: new loss; Abstract: A differentiable learning loss; Introduction: supervise ...

  10. Qt多线程实现思路一

    实现一个线程开启时,时间计时器任然能够计数,两路独立工作的线程功能.从类的帮助文件中可以看到如下信息,静态公共成员函数和保护类型的函数,在静态函数中有睡眠函数msleep毫秒级,sleep秒级,usl ...