[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. selenium实现网易邮箱的登录注册

    #实现163网站的注册 from selenium import webdriver import time driver = webdriver.Chrome() url = 'https://ma ...

  2. git commond 详解

    Git commit git commit 主要是将暂存区里的改动给提交到本地的版本库.每次使用git commit 命令我们都会在本地版本库生成一个40位的哈希值,这个哈希值也叫commit-id, ...

  3. Intel 8086 标志寄存器及JCC指令表

    汇编 JCC指令表 JCC指条件跳转指令,CC就是指条件码. JCC指令 中文含义 英文原意 检查符号位 典型C应用 JZ/JE 若为0则跳转:若相等则跳转 jump if zero;jump if ...

  4. 安装MYSQL到Ubuntu(APT)

    运行环境 系统版本:Ubuntu 16.04.6 LTS 软件版本:MYSQL-5.7 硬件要求:无 安装过程 1.安装APT-MYSQL存储库 APT-MYSQL存储库由MYSQL官网提供.选择安装 ...

  5. Markdown数学公式如何打出回归符号

    来源:https://blog.csdn.net/garfielder007/article/details/51646604 函数.符号及特殊字符 语法 效果 语法 效果 语法 效果 \bar{x} ...

  6. GitKraken 快速配置 SSH Key

    快速使用 GitKraken 配置SSH keys git是现在最流行的版本管理工具,应用范围非常广泛,推荐一款git的可视化工具,这款 工具特别方便 它的官方如下https://www.gitkra ...

  7. Spark学习之路 (十一)SparkCore的调优之Spark内存模型[转]

    概述 Spark 作为一个基于内存的分布式计算引擎,其内存管理模块在整个系统中扮演着非常重要的角色.理解 Spark 内存管理的基本原理,有助于更好地开发 Spark 应用程序和进行性能调优.本文旨在 ...

  8. vs2015运行时提示未加载vcruntime140.adm64.pb

    后调试查看发现 vs2015运行时提示未加载vcruntime140.adm64.pb 解决方案:去微软官网下载安装 vc_redist.exe ,安装就可以了.有64位版和32位版,根据计算机配置进 ...

  9. windows下web端测试环境搭建(tomcat+oracle)

    一.安装oracle数据库 1.关闭防火墙.360安全卫士,运行安装程序:Setup.exe,然后下一步...... 2.安装完成后,检查服务是否已启动 3.cmd输入验证登录成功:sqlplus s ...

  10. cartographer保存地图

    手持激光,并用cartographer建图,保存的地图是.pbstream格式 ht@ht:~$ rosservice call /write_state /home/ht/Desktop/carto ...