传送门

另一个传送门

终于把心头大恨切掉了……后缀自动机大法好,从此抛弃后缀数组哈哈……(说的跟你会写后缀数组似的

好像网上的题解大多都是后缀数组?看了看表示理解不能,那我这份后缀自动机的题解就写详细点好了……

题目跟LCP有关,不难想到后缀树,对反串建后缀自动机之后得到的parent树就是原串的后缀树,之后的操作就在parent树上乱搞就行了。

询问都是询问s[a..b]中的所有子串和s[c..d]的LCP长度的最大值,显然s[a..b]的子串可以直接改成s[a..b]的后缀,那么就有

$ans=\min\{\max_{a\le i\le b}\{\min\{LCP(i,c),b-i+1\}\},d-c+1\}$

记黑点为每个前缀对应的节点,如果没有b-i+1的限制的话,问题就变成了每次询问c与所有编号位于[a,b]的黑点的所有LCA中深度最大的那一个的深度,显然是可以直接上主席树+倍增的,单次询问$O(log^2n)$(@树白黑)。

现在有了b-i+1的限制,可以二分答案,设当前答案为M,任务就变成了判定答案能否$\ge M$。显然只有$b-i+1\ge M$的i合法(即可以使答案$\ge M$),移项得$i\le b-M+1$,再加上$a\le i\le b$的限制即可得出合法的i的范围,再用倍增找到最浅的深度$\ge M$的点(因为这个点要作为深度最小的LCA,或者是这个LCA的祖先),询问一下这个点的子树中是否存在一个编号在合法范围内的黑点即可(因为这个点一定是c的祖先,因此只要子树中有黑点就说明深度最小的LCA不会比它浅),有则说明答案$\ge M$,否则说明答案<M,调整下一次二分即可。

询问子树中是否有黑点可以用主席树,那么每次判定的复杂度就是倍增$O(logn)$+主席树$O(logn)$=$O(logn)$,加上二分答案后单次询问$O(log^2n)$,还是在线算法(虽然大多都是在线不过听说有写离线的……?)

代码里的二分可以保证$M\ge 1$,因此没有判$i\le b$的限制。

 /**************************************************************
Problem: 4556
User: hzoier
Language: C++
Result: Accepted
Time:12996 ms
Memory:120180 kb
****************************************************************/
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int maxn=;
void expand(int);
void dfs(int);
void build(int,int,int&,int);
void query(int,int,int,int);
vector<int>G[maxn];
int SAM_root,last,SAM_cnt=,val[maxn]={},par[maxn]={},go[maxn][]={{}};
int sm[maxn<<],lc[maxn<<],rc[maxn<<]={},cnt=,root[maxn]={};
int f[maxn][]={{}},dfn[maxn],finish[maxn],tim=;
char S[maxn];
int n,m,iter[maxn],k=,a,b,c,d=,x,r,s,t,tmp;
int main(){
SAM_root=last=++SAM_cnt;
scanf("%d%d%s",&n,&m,S+);
for(int i=n;i;i--){
expand(S[i]-'a');
iter[i]=last;
}
for(int i=;i<=SAM_cnt;i++)G[par[i]].push_back(i);
dfs(SAM_root);
for(int i=;i<=n;i++){
x=dfn[iter[i]];
build(,tim,root[i],root[i-]);
}
for(int j=;j<=k;j++)for(int i=;i<=tim;i++)f[i][j]=f[f[i][j-]][j-];
while(m--){
scanf("%d%d%d%d",&a,&b,&c,&d);
int L=,R=b-a+;
while(L<=R){
int M=(L+R)>>;
x=iter[c];
tmp=;
if(val[x]>=M){
for(int i=k;i>=;i--)if(val[f[x][i]]>=M)x=f[x][i];
s=dfn[x];
t=finish[x];
if(a<=b-M+)query(,tim,root[b-M+],root[a-]);
}
if(tmp)L=M+;
else R=M-;
}
printf("%d\n",min(R,d-c+));
}
return ;
}
void expand(int c){
int p=last,np=++SAM_cnt;
val[np]=val[p]+;
while(p&&!go[p][c]){
go[p][c]=np;
p=par[p];
}
if(!p)par[np]=SAM_root;
else{
int q=go[p][c];
if(val[q]==val[p]+)par[np]=q;
else{
int nq=++SAM_cnt;
val[nq]=val[p]+;
memcpy(go[nq],go[q],sizeof(go[q]));
par[nq]=par[q];
par[np]=par[q]=nq;
while(p&&go[p][c]==q){
go[p][c]=nq;
p=par[p];
}
}
}
last=np;
}
void dfs(int x){
dfn[x]=++tim;
d++;
while((<<k)<d)k++;
for(int i=;i<(int)G[x].size();i++){
f[G[x][i]][]=x;
dfs(G[x][i]);
}
finish[x]=tim;
d--;
}
void build(int l,int r,int &rt,int pr){
sm[rt=++cnt]=sm[pr]+;
if(l==r)return;
lc[rt]=lc[pr];
rc[rt]=rc[pr];
int mid=(l+r)>>;
if(x<=mid)build(l,mid,lc[rt],lc[pr]);
else build(mid+,r,rc[rt],rc[pr]);
}
void query(int l,int r,int rt,int pr){
if(!rt&&!pr)return;
if(s<=l&&t>=r){
tmp+=sm[rt]-sm[pr];
return;
}
int mid=(l+r)>>;
if(s<=mid)query(l,mid,lc[rt],lc[pr]);
if(t>mid)query(mid+,r,rc[rt],rc[pr]);
}

一个细节:

一开始觉得二分答案可以直接换成一边倍增上跳一边判定当前点是否可行,后来发现这样是错的,因为答案不一定是c的某个祖先的深度(比如有个点深度是2,父亲的深度是0,可是答案是1)……当然判定当前点是否可行的时候再二分一下也可以,不过这样好像会多一个log……

话说很久之前就想写这题了,然后题意各种弄不清+网上的后缀数组题解各种看不懂=无限期跳票,今天心血来潮读了一遍题才弄清题意,然后找了几份后缀数组的题解还是没怎么看懂……无奈自己脑补了一发后缀自动机的做法,然而为啥跑得这么慢……明明是同样的做法,我比ad学长慢了整整2s,比后缀数组众更是慢到不知哪里去了……

Tjoi2016&Heoi2016 字符串的更多相关文章

  1. Bzoj 4556: [Tjoi2016&Heoi2016]字符串

    4556: [Tjoi2016&Heoi2016]字符串 Time Limit: 20 Sec  Memory Limit: 128 MBSubmit: 177  Solved: 92[Sub ...

  2. Bzoj4556: [Tjoi2016&Heoi2016]字符串 后缀数组

    4556: [Tjoi2016&Heoi2016]字符串 Time Limit: 20 Sec  Memory Limit: 128 MBSubmit: 169  Solved: 87[Sub ...

  3. 4556: [Tjoi2016&Heoi2016]字符串

    4556: [Tjoi2016&Heoi2016]字符串 链接 分析: 首先可以二分这个长度.此时需要判断是否存在一个以b结尾的前缀,满足与[c,d]的lcp大于等于mid. 如果我们把串翻转 ...

  4. [BZOJ4556][Tjoi2016&Heoi2016]字符串 后缀数组+主席树

    4556: [Tjoi2016&Heoi2016]字符串 Time Limit: 20 Sec  Memory Limit: 128 MB Description 佳媛姐姐过生日的时候,她的小 ...

  5. 【BZOJ4556】[Tjoi2016&Heoi2016]字符串 后缀数组+二分+主席树+RMQ

    [BZOJ4556][Tjoi2016&Heoi2016]字符串 Description 佳媛姐姐过生日的时候,她的小伙伴从某东上买了一个生日礼物.生日礼物放在一个神奇的箱子中.箱子外边写了一 ...

  6. [BZOJ4556][TJOI2016&&HEOI2016]字符串(二分答案+后缀数组+RMQ+主席树)

    4556: [Tjoi2016&Heoi2016]字符串 Time Limit: 20 Sec  Memory Limit: 128 MBSubmit: 1360  Solved: 545[S ...

  7. [BZOJ4556][Tjoi2016&Heoi2016]字符串 主席树+二分+倍增+后缀自动机

    4556: [Tjoi2016&Heoi2016]字符串 Time Limit: 20 Sec  Memory Limit: 128 MBSubmit: 1215  Solved: 484[S ...

  8. BZOJ4556: [Tjoi2016&Heoi2016]字符串

    Description 佳媛姐姐过生日的时候,她的小伙伴从某东上买了一个生日礼物.生日礼物放在一个神奇的箱子中.箱子外边写了 一个长为n的字符串s,和m个问题.佳媛姐姐必须正确回答这m个问题,才能打开 ...

  9. BZOJ4556 [Tjoi2016&Heoi2016]字符串 SA ST表 二分答案 主席树

    原文链接https://www.cnblogs.com/zhouzhendong/p/BZOJ4556.html 题目传送门 - BZOJ4556 题意 给定一个长度为 $n$ 的字符串 $s$ . ...

  10. 2019.02.27 bzoj4556: [Tjoi2016&Heoi2016]字符串(二分答案+sam+线段树合并)

    传送门 题意:给一个字符串SSS. 有mmm次询问,每次给四个参数a,b,c,da,b,c,da,b,c,d,问s[a...b]s[a...b]s[a...b]的所有子串和s[x...y]s[x... ...

随机推荐

  1. CF1097D Makoto and a Blackboard(期望)

    [Luogu-CF1097D] 给定 \(n,k\)一共会进行 \(k\) 次操作 , 每次操作会把 \(n\) 等概率的变成 \(n\) 的某个约数 求操作 \(k\) 次后 \(n\) 的期望是多 ...

  2. pipenv 虚拟环境新玩法

    首先,虚拟环境本质是一个文件,是为了适应不同的项目而存在.pipenv相当于virtualenv和pip的合体. pipenv主要有以下特性: (1)pipenv集成了pip,virtualenv两者 ...

  3. 分分钟钟学会Python - 数据类型(dict)

    今日内容 字典(dict) 具体内容 1.字典含义 帮助用户去表示一个事物的信息(事物是有多个属性). 基本格式 data = {键:值,键:值,键:值,键:值,键:值,键:值,} # 练习题 use ...

  4. pycharm入门的简易使用教程

    1.安装python3.5包: 本人安装到C盘,如下所示: Ps:安装时候,路径一定要添加到环境变量中,安装完,需要更新环境变量,所以要重启电脑 2.安装pycharm 下载相应的安装包,步骤不重复了 ...

  5. Angular 2 升级到 Angular 5

    Angular 2 升级到 Angular 5 ts文件最上面的import语句里不要添加 .ts 后缀 , 不然 npm start 编译会失败 . 虽然浏览器能打开项目的URL , 但是内容会丢失 ...

  6. SAP Sybase IQ 操作基础

    1.启动 source IQ-16_0.sh 命令行查看安装程序是否成功 start_iq -v2 2.数据库.表空间 start_iq -n utility_db dbisql -c 'uid=db ...

  7. oracle--dump->buffer cache (dump 深入实践一)

    1,dump 取值 ALTER SESSION SET EVENTS 'immediate trace name buffers level n'; 只转储buffer header. 在level ...

  8. Robot Framework(AutoItLibrary安装)

    RobotFramework下安装AutoItLibrary库 1.安装pythonwin32 在下载地址:http://sourceforge.net/projects/pywin32/files/ ...

  9. Linux 命令学习之ls

    ls(list) 功能说明: ls 命令是Linux中使用最频繁的命令,即list的缩写,默认情况下会罗列出当前文件下的所有文件.同时ls 也可以指定罗列某个文件下的文件.而且该命令可以查看文件的一些 ...

  10. 进入与退出anconda虚拟环境

    # 进入虚拟环境,使用 # # $ conda activate spider-venv # # 退出虚拟环境,使用 # # $ conda deactivate