[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. lucas定理及其拓展的推导

    lucas定理及其拓展的推导 我的前一篇博客-- lucas定理 https://mp.csdn.net/mdeditor/100550317#主要是给出了lucas的结论和模板,不涉及推导. 本篇文 ...

  2. 剑指offer-面试题46-把数字翻译成字符串-动态规划

    /* 题目: 给定一个数字,将0~25依次翻译为a~z,计算有多少种翻译方法. */ /* 思路: 动态规划法 */ #include<iostream> #include<cstr ...

  3. Postman实现文件下载功能测试

    背景 实现一个模板下载的功能,然后想用postman进行文件下载的功能测试 实现 然后会弹出下载框 tips:我第一次点的时候,没有任何反应,以为是卡死了,后来发现是弹出的下载框在postman框下面 ...

  4. Wannafly Winter Camp 2020 Day 6G 单调栈 - 贪心

    对于排列 \(p\),它的单调栈 \(f\) 定义为,\(f_i\) 是以 \(p_i\) 结尾的最长上升子序列的长度 先给定 \(f\) 中一些位置的值,求字典序最小的 \(p\) 使得它满足这些值 ...

  5. 使用SMTP协议发送邮件

    class Program { static void Main(string[] args) { ) { try { inputmodel obj = new inputmodel(args); S ...

  6. JAVA 递归线程池测试 ExecutorService / ForkJoinPool

    测试工具使用递归的方式获取子进程的Msg消息,目前有2种常用的ExecutorService / ForkJoinPool 为了测试哪种效果较好,我们来写个测试Demo,循环5555555次+1(加锁 ...

  7. ng-项目结构

    ng启动过程 目录结构 . ├── e2e 端到端测试(暂且不关心) ├── node_modules npm安装的第三方包 ├── src 存放业务源码 ├── .angular-cli.json ...

  8. Codeforces Round #613 (Div. 2) (A-E)

    A略 直接求和最大的子序列即可(注意不能全部选中整个子序列) or #include<bits/stdc++.h> using namespace std; void solve(){ i ...

  9. Android View框架的draw机制

    概述 Android中View框架的工作机制中,主要有三个过程: 1.View树的测量(measure) Android View框架的measure机制 2.View树的布局(layout)Andr ...

  10. Spring-Cache手动清缓存

    Spring Cache 手动清Redis缓存 注册cacheRedisTemplate 将 cache 的 RedisTemplate 注册为Bean @Bean(name = "cach ...