佳媛姐姐过生日的时候,她的小伙伴从某东上买了一个生日礼物。生日礼物放在一个神奇的箱子中。箱子外边写了

一个长为\(n\)的字符串\(s\),和\(m\)个问题。佳媛姐姐必须正确回答这\(m\)个问题,才能打开箱子拿到礼物,升职加薪,出任CE

O,嫁给高富帅,走上人生巅峰。每个问题均有\(a,b,c,d\)四个参数,问你子串\(s[a..b]\)的所有子串和\(s[c..d]\)的最长公

共前缀的长度的最大值是多少?佳媛姐姐并不擅长做这样的问题,所以她向你求助,你该如何帮助她呢?

Input

输入的第一行有两个正整数\(n\),\(m\),分别表示字符串的长度和询问的个数。接下来一行是一个长为\(n\)的字符串。接下来

\(m\)行,每行有4个数\(a\),\(b\),\(c\),\(d\),表示询问\(s[a..b]\)的所有子串和\(s[c..d]\)的最长公共前缀的最大值。\(1<=n,m<=100,000\),

字符串中仅有小写英文字母,\(a<=b,c<=d\),\(1<=a,b,c,d<=n\)

Output

对于每一次询问,输出答案。

Sample Input

5 5
aaaaa
1 1 1 5
1 5 1 1
2 3 2 3
2 4 2 3
2 3 2 4

Sample Output

1
1
2
2
2

题意:

中文题面,不解释

题解:

首先前缀不好处理,我们把字符串翻转。

然后发现很难直接求出答案,就采取二分最长后缀并验证。我们只需要验证左区间的后缀是否出现在右区间就行了。

然后用倍增在\(parent\)树上找到对应该子串的节点,并求出该节点能覆盖的节点(他子树中的节点),所以我们可以用线段树合并来求解。

#include<bits/stdc++.h>
#define mid (l+(r-l)/2)
using namespace std;
const int N=200010;
int lson[N*30],rson[N*30],sum[N*30];
int n,m,head[N],nxt[N],bian[N],p,q,np,nq,mp[N],pos[N],cnt,last;
int root[N],ch[N][27],fa[N],l[N],tot,sz,f[N][19],dep[N];
char s[N];
void ins(int x)
{
int c=s[x]-'a'+1;
p=last; np=++cnt; last=np; mp[np]=x; pos[x]=np;
l[np]=l[p]+1;
for (;p&&!ch[p][c];p=fa[p]) ch[p][c]=np;
if (!p) fa[np]=1;
else {
q=ch[p][c];
if (l[p]+1==l[q]) fa[np]=q;
else {
nq=++cnt; l[nq]=l[p]+1;
memcpy(ch[nq],ch[q],sizeof(ch[q]));
fa[nq]=fa[q];
fa[q]=fa[np]=nq;
for (;ch[p][c]==q;p=fa[p]) ch[p][c]=nq;
}
}
}
void add(int x,int y){
tot++,bian[tot]=y,nxt[tot]=head[x],head[x]=tot;
}
int updata(int pre,int l,int r,int x){
int rt=++sz; sum[rt]=sum[pre]+1;
if (l==r) return rt;
lson[rt]=lson[pre];
rson[rt]=rson[pre];
if (x<=mid) lson[rt]=updata(lson[pre],l,mid,x);
else rson[rt]=updata(rson[pre],mid+1,r,x);
return rt;
}
void pushup(int x){
int l=lson[x],r=rson[x];
sum[x]=sum[l]+sum[r];
}
int merge(int x,int y){
if(!x||!y)return x+y;
int rt=++sz;
lson[rt]=merge(lson[x],lson[y]);
rson[rt]=merge(rson[x],rson[y]);
pushup(rt);
return rt;
}
void dfs(int x){
for (int i=1;i<=17;i++){
if (dep[x]-(1<<i)<0) break;
f[x][i]=f[f[x][i-1]][i-1];
}
for (int i=head[x];i;i=nxt[i]){
f[bian[i]][0]=x,dep[bian[i]]=dep[x]+1;
dfs(bian[i]);
root[x]=merge(root[x],root[bian[i]]);
}
}
int query(int rt,int l,int r,int x,int y){
if(x<=l&&r<=y)return sum[rt];
int ans=0;
if(x<=mid)ans+=query(lson[rt],l,mid,x,y);
if(y>mid)ans+=query(rson[rt],mid+1,r,x,y);
return ans;
}
bool check(int k,int left,int right,int x){
if(k==0)return 1;
for(int rt=17;rt>=0;rt--)
if(l[f[x][rt]]>=k)x=f[x][rt];
return query(root[x],1,n,left,right);
}
int main(){
cin>>n>>m;
cin>>s+1;reverse(s+1,s+n+1);
last=++cnt;
for(int i=1;i<=n;++i)ins(i);
for(int i=1;i<=cnt;++i)add(fa[i],i);
for(int i=1;i<=cnt;++i)
if(mp[i])root[i]=updata(root[i],1,n,mp[i]);
dep[1]=1; dfs(1);
for (int i=1;i<=m;++i){
int a,b,c,d;
scanf("%d%d%d%d",&a,&b,&c,&d);
swap(a,b); swap(c,d);
a=n-a+1; b=n-b+1; c=n-c+1; d=n-d+1;
int l=0,r=min(d-c+1,b-a+1),ans=0;
while (l<=r){
if (check(mid,a+mid-1,b,pos[d])) ans=max(ans,mid),l=mid+1;
else r=mid-1;
}
printf("%d\n",ans);
}
}

字符串(tjoi2016,heoi2016,bzoj4556)(sam(后缀自动机)+线段树合并+倍增+二分答案)的更多相关文章

  1. HDU - 6704 K-th occurrence (后缀数组+主席树/后缀自动机+线段树合并+倍增)

    题意:给你一个长度为n的字符串和m组询问,每组询问给出l,r,k,求s[l,r]的第k次出现的左端点. 解法一: 求出后缀数组,按照排名建主席树,对于每组询问二分或倍增找出主席树上所对应的的左右端点, ...

  2. 【BZOJ4556】[TJOI2016&HEOI2016] 字符串(后缀自动机+线段树合并+二分)

    点此看题面 大致题意: 给你一个字符串\(s\),每次问你一个子串\(s[a..b]\)的所有子串和\(s[c..d]\)的最长公共前缀. 二分 首先我们可以发现一个简单性质,即要求最长公共前缀,则我 ...

  3. 模板—字符串—后缀自动机(后缀自动机+线段树合并求right集合)

    模板—字符串—后缀自动机(后缀自动机+线段树合并求right集合) Code: #include <bits/stdc++.h> using namespace std; #define ...

  4. bzoj5417/luoguP4770 [NOI2018]你的名字(后缀自动机+线段树合并)

    bzoj5417/luoguP4770 [NOI2018]你的名字(后缀自动机+线段树合并) bzoj Luogu 给出一个字符串 $ S $ 及 $ q $ 次询问,每次询问一个字符串 $ T $ ...

  5. BZOJ3413: 匹配(后缀自动机 线段树合并)

    题意 题目链接 Sol 神仙题Orz 后缀自动机 + 线段树合并... 首先可以转化一下模型(想不到qwq):问题可以转化为统计\(B\)中每个前缀在\(A\)中出现的次数.(画一画就出来了) 然后直 ...

  6. cf666E. Forensic Examination(广义后缀自动机 线段树合并)

    题意 题目链接 Sol 神仙题Orz 后缀自动机 + 线段树合并 首先对所有的\(t_i\)建个广义后缀自动机,这样可以得到所有子串信息. 考虑把询问离线,然后把\(S\)拿到自动机上跑,同时维护一下 ...

  7. [Luogu5161]WD与数列(后缀数组/后缀自动机+线段树合并)

    https://blog.csdn.net/WAautomaton/article/details/85057257 解法一:后缀数组 显然将原数组差分后答案就是所有不相交不相邻重复子串个数+n*(n ...

  8. 【BZOJ-4556】字符串 后缀数组+二分+主席树 / 后缀自动机+线段树合并+二分

    4556: [Tjoi2016&Heoi2016]字符串 Time Limit: 20 Sec  Memory Limit: 128 MBSubmit: 657  Solved: 274[Su ...

  9. [NOI2018]你的名字(后缀自动机+线段树合并)

    看到题目名字去补番是种怎么样的体验 我只会 \(68\) 分,打了个暴力.正解看了一会儿,发现跟 \([HEOI2016/TJOI2016]\) 字符串很像,用线段树合并维护 \(endpos\) 集 ...

随机推荐

  1. python yaml

    一.安装PyYAML http://pyyaml.org/ 二.入门参考 http://www.cnblogs.com/c9com/archive/2013/01/05/2845539.html ht ...

  2. oracle的分析函数over(Partition by...) 及开窗函数

        over(Partition by...) 一个超级牛皮的ORACLE特有函数. oracle的分析函数over 及开窗函数一:分析函数overOracle从8.1.6开始提供分析函数,分析函 ...

  3. Java WebService 知识点汇总

    java webservice 获取传入IP axis.jar    servlet.jar MessageContext mMsgContext = MessageContext.getCurren ...

  4. DefaultSingletonBeanRegistry

    DefaultSingletonBeanRegistry 这是 DefaultSingletonBeanRegistry 类的体系结构,由一个类一个责任的原则: AliasRegistry : 提供别 ...

  5. VS2015 python

    http://pgqlife.info/2015/05/05/VS-Python/ 配置文档

  6. java日期正则表达式精准校验

      function checkDate(obj) {       var date=obj.value;     var re = new RegExp("(([0-9]{3}[1-9]| ...

  7. /etc/inittab加入自动启动格式

    R01:35:respawn:/usr/bin/exe_program 说明 R01:标识,每一行必须唯一(R01并无特殊含义,可自定义). 35:有效模式,3字符界面启动,5图形界面启动 respa ...

  8. linux下第一个C程序

    首先,用vi编辑器新建一个文件 $vi hi.c 输入以下的程序(怎么用vi不说了) #include <stdio.h> int main() { printf("hello. ...

  9. 2018.06.29 洛谷P1505 [国家集训队]旅游(树链剖分)

    旅游 题目描述 Ray 乐忠于旅游,这次他来到了T 城.T 城是一个水上城市,一共有 N 个景点,有些景点之间会用一座桥连接.为了方便游客到达每个景点但又为了节约成本,T 城的任意两个景点之间有且只有 ...

  10. win 控制台工作路径切换

    1.如果是同磁盘 直接cd 列如cd C:\mysql\bin 2.如果不是同一磁盘 则要2.1 d: 操作 2.2 cd D:\Software\xampp\address\mysql\bin ps ...