洛谷 P4094 [HEOI2016/TJOI2016]字符串(SA+主席树)
一道码农题…………
u1s1 感觉这类题目都挺套路的,就挑个有代表性的题写一篇题解罢。
首先注意到答案满足可二分性,故考虑二分答案 \(mid\),转化为判定性问题。
考虑怎样检验 \(mid\) 是否可行,它等价于是否存在 \(s[a...b]\) 中的一个子串 \(t\) 满足 \(s[c...c+mid-1]\) 为 \(t\) 的前缀。不过不难发现这个“前缀”是假的,因为 \(\forall a\le l\le r\le b\),\(s[l...r]\) 的任意一个前缀都是 \(s[a...b]\) 的子串,故只需检验 \(s[c...c+mid-1]\) 是否为 \(s[a...b]\) 的子串。而根据 LCP Lemma 的知识可知这个东西又可以转化为 \(\exist x\in[a,b-mid+1]\) 使得 \(\text{LCP}(x,c)\geq mid\)。
很明显满足 \(\text{LCP}(x,c)\geq mid\) 的 \(x\) 在字典序上一定是一段连续的区间 \([L,R]\),这个区间可以通过二分+ST 表求出,因此 \(\exist x\in[a,b-mid+1],\text{LCP}(x,c)\ge mid\Leftrightarrow\exist i\in[L,R],sa_i\in[a,b-mid+1]\)。考虑以后缀编号为下标建立主席树,第 \(i\) 棵树上 \([l,r]\) 的区间中表示在字典序前 \(i\) 的后缀中有多少个属于区间 \([l,r]\),然后在编号 \(R,L-1\) 为下标的两棵主席树上查询下标为 \([a,b-mid+1]\) 中的数之和并相减,判断是否 \(>0\) 即可。
时间复杂度 \(\mathcal O(n\log^2n)\)。常数巨大……
#include <bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define fill0(a) memset(a,0,sizeof(a))
#define fill1(a) memset(a,-1,sizeof(a))
#define fillbig(a) memset(a,63,sizeof(a))
#define pb push_back
#define ppb pop_back
#define mp make_pair
template<typename T1,typename T2> void chkmin(T1 &x,T2 y){if(x>y) x=y;}
template<typename T1,typename T2> void chkmax(T1 &x,T2 y){if(x<y) x=y;}
typedef pair<int,int> pii;
typedef long long ll;
namespace fastio{
#define FILE_SIZE 1<<23
char rbuf[FILE_SIZE],*p1=rbuf,*p2=rbuf,wbuf[FILE_SIZE],*p3=wbuf;
inline char getc(){return p1==p2&&(p2=(p1=rbuf)+fread(rbuf,1,FILE_SIZE,stdin),p1==p2)?-1:*p1++;}
inline void putc(char x){(*p3++=x);}
template<typename T> void read(T &x){
x=0;char c=getchar();T neg=0;
while(!isdigit(c)) neg|=!(c^'-'),c=getchar();
while(isdigit(c)) x=(x<<3)+(x<<1)+(c^48),c=getchar();
if(neg) x=(~x)+1;
}
template<typename T> void recursive_print(T x){if(!x) return;recursive_print(x/10);putc(x%10^48);}
template<typename T> void print(T x){if(!x) putc('0');if(x<0) putc('-'),x=~x+1;recursive_print(x);}
void print_final(){fwrite(wbuf,1,p3-wbuf,stdout);}
}
const int MAXN=2e5;
const int MAXP=MAXN*40;
const int LOG_N=18;
int n,qu;char s[MAXN+5];pii x[MAXN+5];
int sa[MAXN+5],rk[MAXN+5],buc[MAXN+5],seq[MAXN+5],ht[MAXN+5];
int st[MAXN+5][LOG_N+2];
void getsa(){
int vmax=122,gr=0;
for(int i=1;i<=n;i++) buc[s[i]]++;
for(int i=1;i<=vmax;i++) buc[i]+=buc[i-1];
for(int i=n;i;i--) sa[buc[s[i]]--]=i;
for(int i=1;i<=n;i++){
if(s[sa[i]]!=s[sa[i-1]]) gr++;
rk[sa[i]]=gr;
} vmax=gr;
for(int k=1;k<=n;k<<=1){
for(int i=1;i<=n;i++){
if(i+k<=n) x[i]=mp(rk[i],rk[i+k]);
else x[i]=mp(rk[i],0);
} memset(buc,0,sizeof(buc));gr=0;int num=0;
for(int i=n-k+1;i<=n;i++) seq[++num]=i;
for(int i=1;i<=n;i++) if(sa[i]>k) seq[++num]=sa[i]-k;
for(int i=1;i<=n;i++) buc[x[i].fi]++;
for(int i=1;i<=vmax;i++) buc[i]+=buc[i-1];
for(int i=n;i;i--) sa[buc[x[seq[i]].fi]--]=seq[i];
for(int i=1;i<=n;i++){
if(x[sa[i]]!=x[sa[i-1]]) gr++;
rk[sa[i]]=gr;
} vmax=gr;if(vmax==n) break;
}
}
void getht(){
int k=0;
for(int i=1;i<=n;i++){
if(rk[i]==1) continue;if(k) k--;
int j=sa[rk[i]-1];
while(i+k<=n&&j+k<=n&&s[i+k]==s[j+k]) k++;
ht[rk[i]]=k;
}
}
void buildst(){
for(int i=1;i<=n;i++) st[i][0]=ht[i];
for(int i=1;i<=LOG_N;i++) for(int j=1;j+(1<<i)-1<=n;j++)
st[j][i]=min(st[j][i-1],st[j+(1<<i-1)][i-1]);
}
int query(int l,int r){
// printf("%d %d\n",l,r);
int k=log2(r-l+1);
return min(st[l][k],st[r-(1<<k)+1][k]);
}
namespace segtree{
struct node{int ch[2],val;} s[MAXP+5];
int ncnt,rt[MAXN+5];
void build(int &k,int l,int r){
k=++ncnt;if(l==r) return;int mid=l+r>>1;
build(s[k].ch[0],l,mid);build(s[k].ch[1],mid+1,r);
}
int modify(int k,int l,int r,int p,int x){
int z=++ncnt;s[z]=s[k];s[z].val+=x;
if(l==r) return z;
int mid=l+r>>1;
if(p<=mid) s[z].ch[0]=modify(s[k].ch[0],l,mid,p,x);
else s[z].ch[1]=modify(s[k].ch[1],mid+1,r,p,x);
return z;
}
int query(int k,int l,int r,int ql,int qr){
if(ql<=l&&r<=qr) return s[k].val;
int mid=(l+r)>>1;
if(qr<=mid) return query(s[k].ch[0],l,mid,ql,qr);
else if(ql>mid) return query(s[k].ch[1],mid+1,r,ql,qr);
else return query(s[k].ch[0],l,mid,ql,mid)+query(s[k].ch[1],mid+1,r,mid+1,qr);
}
}
using segtree::rt;
using segtree::modify;
using segtree::build;
bool check(int x,int a,int b,int c){
b=b-x+1;
int L=1,R=rk[c]-1,mid,l=rk[c],r=rk[c];
while(L<=R) (query((mid=L+R>>1)+1,rk[c])>=x)?l=mid,R=mid-1:L=mid+1;
L=rk[c]+1,R=n;
while(L<=R) (query(rk[c]+1,mid=L+R>>1)>=x)?r=mid,L=mid+1:R=mid-1;
// printf("%d %d %d %d %d\n",c,x,l,r,segtree::query(rt[r],1,n,a,b)-segtree::query(rt[l-1],1,n,a,b));
return segtree::query(rt[r],1,n,a,b)-segtree::query(rt[l-1],1,n,a,b);
}
int main(){
scanf("%d%d%s",&n,&qu,s+1);
getsa();getht();buildst();build(rt[0],1,n);
// printf("%d\n",query(8,9));
// for(int i=1;i<=n;i++) printf("%d\n",sa[i]);
for(int i=1;i<=n;i++) rt[i]=modify(rt[i-1],1,n,sa[i],1);
while(qu--){
int a,b,c,d;scanf("%d%d%d%d",&a,&b,&c,&d);
int l=0,r=min(b-a+1,d-c+1),mid,x=0;
while(l<=r) check(mid=l+r>>1,a,b,c)?x=mid,l=mid+1:r=mid-1;
printf("%d\n",x);
}
return 0;
}
/*
13 1
ababbabbbaaab
2 4 8 10
*/
洛谷 P4094 [HEOI2016/TJOI2016]字符串(SA+主席树)的更多相关文章
- 洛谷P2824 [HEOI2016/TJOI2016]排序(线段树)
传送门 这题的思路好清奇 因为只有一次查询,我们考虑二分这个值为多少 将原序列转化为一个$01$序列,如果原序列上的值大于$mid$则为$1$否则为$0$ 那么排序就可以用线段树优化,设该区间内$1$ ...
- 洛谷 P2824 [HEOI2016/TJOI2016]排序 (线段树合并)
(另外:题解中有一种思路很高妙而且看上去可以适用一些其他情况的离线方法) 线段树合并&复杂度的简单说明:https://blog.csdn.net/zawedx/article/details ...
- 洛谷 P4093 [HEOI2016/TJOI2016]序列 CDQ分治优化DP
洛谷 P4093 [HEOI2016/TJOI2016]序列 CDQ分治优化DP 题目描述 佳媛姐姐过生日的时候,她的小伙伴从某宝上买了一个有趣的玩具送给他. 玩具上有一个数列,数列中某些项的值可能会 ...
- P4094 [HEOI2016/TJOI2016]字符串 后缀数组+主席树+二分答案
$ \color{#0066ff}{ 题目描述 }$ 佳媛姐姐过生日的时候,她的小伙伴从某东上买了一个生日礼物.生日礼物放在一个神奇的箱子中.箱子外边写了一个长为n的字符串s,和m个问题.佳媛姐姐必须 ...
- BZOJ4553/洛谷P4093 [HEOI2016/TJOI2016]序列 动态规划 分治
原文链接http://www.cnblogs.com/zhouzhendong/p/8672434.html 题目传送门 - BZOJ4553 题目传送门 - 洛谷P4093 题解 设$Li$表示第$ ...
- 洛谷 P4091 [HEOI2016/TJOI2016]求和 解题报告
P4091 [HEOI2016/TJOI2016]求和 题目描述 在2016年,佳媛姐姐刚刚学习了第二类斯特林数,非常开心. 现在他想计算这样一个函数的值: \[ f(n)=\sum_{i=0}^n\ ...
- 洛谷 P4093 [HEOI2016/TJOI2016]序列 解题报告
P4093 [HEOI2016/TJOI2016]序列 题目描述 佳媛姐姐过生日的时候,她的小伙伴从某宝上买了一个有趣的玩具送给他.玩具上有一个数列,数列中某些项的值可能会变化,但同一个时刻最多只有一 ...
- 洛谷 P2824 [HEOI2016/TJOI2016]排序 解题报告
P2824 [HEOI2016/TJOI2016]排序 题意: 有一个长度为\(n\)的1-n的排列\(m\)次操作 \((0,l,r)\)表示序列从\(l\)到\(r\)降序 \((1,l,r)\) ...
- BZOJ4556 HEOI2016/TJOI2016字符串 (后缀树+主席树)
二分答案后相当于判断一个区间的后缀与某个后缀的最长公共前缀是否能>=ans.建出后缀树,在上述问题中后者所在节点向上倍增的跳至len>=ans的最高点,然后相当于查询子树中是否有该区间的节 ...
随机推荐
- python 工具箱
strip() 方法可以从字符串去除不想要的空白符. print() BIF的file参数控制将数据发送/保存到哪里. finally组总会执行,而不论try/except语句中出现什么异常. 会向e ...
- Golang通脉之方法
方法和接收者 Go语言中的方法(Method)是一种作用于特定类型变量的函数.这种特定类型变量叫做接收者(Receiver).接收者的概念就类似于其他语言中的this或者 self. Go 语言中同时 ...
- RabbitMQ设计原理解析
背景 RabbitMQ现在用的也比较多,但是没有过去那么多啦.现在很多的流行或者常用技术或者思路都是从过去的思路中演变而来的.了解一些过去的技术,对有些人来说可能会产生众里寻他千百度的顿悟,加深对技术 ...
- BUAA 2020 软件工程 个人项目作业
BUAA 2020 软件工程 个人项目作业 Author: 17373051 郭骏 项目 内容 这个作业属于哪个课程 2020春季计算机学院软件工程(罗杰 任健) 这个作业的要求在哪里 个人项目作业 ...
- 用cmd命令行创建vue项目模板
1.进入cmd命令行 输入存放项目的位置 2.通过vue create 项目名称 创建项目 3.选择Manually select features 4.通过空格选中第1.2.5.6.7.去掉8 4. ...
- 斐波那契数列 牛客网 剑指Offer
斐波那契数列 牛客网 剑指Offer 题目描述 大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项(从0开始,第0项为0). n<=39 class Solution: ...
- 近期业务大量突增微服务性能优化总结-4.增加对于同步微服务的 HTTP 请求等待队列的监控
最近,业务增长的很迅猛,对于我们后台这块也是一个不小的挑战,这次遇到的核心业务接口的性能瓶颈,并不是单独的一个问题导致的,而是几个问题揉在一起:我们解决一个之后,发上线,之后发现还有另一个的性能瓶颈问 ...
- Linux系统编程之进程概念
注:本文部分图片来源于网络,如有侵权,请告知删除 1. 什么是进程? 在了解进程概念之前,我们需要先知道程序的概念. 程序,是指编译好的二进制文件,这些文件在磁盘上,并不占用系统资源. 进程,指的是一 ...
- RPC 框架 Dubbo 从理解到使用(二)
本篇文章为系列文章,未读第一集的同学请猛戳这里:RPC 框架 Dubbo 从理解到使用(一) 本篇文章讲解 Dubbo 支持的注册中心.Dubbo 负载均衡策略和 Dubbo 控制台的安装. 注册中心 ...
- 聊聊sql优化的15个小技巧
前言 sql优化是一个大家都比较关注的热门话题,无论你在面试,还是工作中,都很有可能会遇到. 如果某天你负责的某个线上接口,出现了性能问题,需要做优化.那么你首先想到的很有可能是优化sql语句,因为它 ...