Tjoi2016&Heoi2016 字符串
终于把心头大恨切掉了……后缀自动机大法好,从此抛弃后缀数组哈哈……(说的跟你会写后缀数组似的
好像网上的题解大多都是后缀数组?看了看表示理解不能,那我这份后缀自动机的题解就写详细点好了……
题目跟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 字符串的更多相关文章
- Bzoj 4556: [Tjoi2016&Heoi2016]字符串
4556: [Tjoi2016&Heoi2016]字符串 Time Limit: 20 Sec Memory Limit: 128 MBSubmit: 177 Solved: 92[Sub ...
- Bzoj4556: [Tjoi2016&Heoi2016]字符串 后缀数组
4556: [Tjoi2016&Heoi2016]字符串 Time Limit: 20 Sec Memory Limit: 128 MBSubmit: 169 Solved: 87[Sub ...
- 4556: [Tjoi2016&Heoi2016]字符串
4556: [Tjoi2016&Heoi2016]字符串 链接 分析: 首先可以二分这个长度.此时需要判断是否存在一个以b结尾的前缀,满足与[c,d]的lcp大于等于mid. 如果我们把串翻转 ...
- [BZOJ4556][Tjoi2016&Heoi2016]字符串 后缀数组+主席树
4556: [Tjoi2016&Heoi2016]字符串 Time Limit: 20 Sec Memory Limit: 128 MB Description 佳媛姐姐过生日的时候,她的小 ...
- 【BZOJ4556】[Tjoi2016&Heoi2016]字符串 后缀数组+二分+主席树+RMQ
[BZOJ4556][Tjoi2016&Heoi2016]字符串 Description 佳媛姐姐过生日的时候,她的小伙伴从某东上买了一个生日礼物.生日礼物放在一个神奇的箱子中.箱子外边写了一 ...
- [BZOJ4556][TJOI2016&&HEOI2016]字符串(二分答案+后缀数组+RMQ+主席树)
4556: [Tjoi2016&Heoi2016]字符串 Time Limit: 20 Sec Memory Limit: 128 MBSubmit: 1360 Solved: 545[S ...
- [BZOJ4556][Tjoi2016&Heoi2016]字符串 主席树+二分+倍增+后缀自动机
4556: [Tjoi2016&Heoi2016]字符串 Time Limit: 20 Sec Memory Limit: 128 MBSubmit: 1215 Solved: 484[S ...
- BZOJ4556: [Tjoi2016&Heoi2016]字符串
Description 佳媛姐姐过生日的时候,她的小伙伴从某东上买了一个生日礼物.生日礼物放在一个神奇的箱子中.箱子外边写了 一个长为n的字符串s,和m个问题.佳媛姐姐必须正确回答这m个问题,才能打开 ...
- BZOJ4556 [Tjoi2016&Heoi2016]字符串 SA ST表 二分答案 主席树
原文链接https://www.cnblogs.com/zhouzhendong/p/BZOJ4556.html 题目传送门 - BZOJ4556 题意 给定一个长度为 $n$ 的字符串 $s$ . ...
- 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... ...
随机推荐
- XMPP Authentication
From: http://www.ietf.org/rfc/rfc2831.txt 2 Authentication The following sections describe how to ...
- Scrapy 抓取股票行情
安装scrapy会出现错误,我们选择anaconda3作为编译环境,搜索scrapy安装(有错误自查) 创建scrapy爬虫项目: 调出cmd,到相应目录:输入: scrapy startprojec ...
- [原创] Shell 参数传递 与 默认值
目录 简介 基本传参 $* 与 $@ 区别 默认参数(变量默认值) if 繁琐方式 - 变量为null = 变量为null时, 同时改变变量值 :- 变量为null 或 空字符串 := 变量为null ...
- .net core集成 vue
1. 在vs 2017 中选中.csproj文件右键修改. 2. 在.csproj 文件中添加如下代码,即表示在构建之前先执行 npm install 和npm run build. <Targ ...
- [前后端分离项目]thinkphp返回给前端数据为字符串
写在前面:现在项目大多是采用前后端分离的模式进行开发,这种模式下的开发大大的提高了工作效率,而进行前后端数据交互传输的格式基本以json为主,毕业设计中兼顾前端开发和后端开发(后端小白一个),前端业务 ...
- cuda9.0 中不存在libnppi.so
编译一个caffe版本,报错找不到 -lnppi 发现使用打是cuda9.0, 但是cuda9.0 中不存在libnppi.so. 只好换成cuda8.0.
- [转]asp:ScriptManager
概述 ScriptManager 控件管理用于 Microsoft ASP.NET AJAX 页面的客户端脚本.默认情况下,ScriptManager 控件将 Microsoft AJAX 库的脚本与 ...
- selenium+Python(Js处理click失效)
有时候元素明明已经找到了,运行也没报错,点击后页面没任何反应.这种问题遇到了,是比较头疼的,因为没任何报错,只是 click 事件失效了. 问题: 1.在练习百度的搜索设置按钮时,点保存设置按钮,al ...
- orcale 之 多表查询
在以往的工作中我们不可能单一的从一张表中查询数据,而在开始设计数据库的时候,我们会把一些数据存放在不同的数据表中,因此往往需要从多个数据表中获取到我们想要的数据. 笛卡儿积 在学习这些之前我们先了解一 ...
- Dictionary<string, object>不区分大小写
Dictionary<string, object> dic = new Dictionary<string, object>(StringComparer.OrdinalIg ...