字符串(tjoi2016,heoi2016,bzoj4556)(sam(后缀自动机)+线段树合并+倍增+二分答案)
佳媛姐姐过生日的时候,她的小伙伴从某东上买了一个生日礼物。生日礼物放在一个神奇的箱子中。箱子外边写了
一个长为\(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(后缀自动机)+线段树合并+倍增+二分答案)的更多相关文章
- HDU - 6704 K-th occurrence (后缀数组+主席树/后缀自动机+线段树合并+倍增)
题意:给你一个长度为n的字符串和m组询问,每组询问给出l,r,k,求s[l,r]的第k次出现的左端点. 解法一: 求出后缀数组,按照排名建主席树,对于每组询问二分或倍增找出主席树上所对应的的左右端点, ...
- 【BZOJ4556】[TJOI2016&HEOI2016] 字符串(后缀自动机+线段树合并+二分)
点此看题面 大致题意: 给你一个字符串\(s\),每次问你一个子串\(s[a..b]\)的所有子串和\(s[c..d]\)的最长公共前缀. 二分 首先我们可以发现一个简单性质,即要求最长公共前缀,则我 ...
- 模板—字符串—后缀自动机(后缀自动机+线段树合并求right集合)
模板—字符串—后缀自动机(后缀自动机+线段树合并求right集合) Code: #include <bits/stdc++.h> using namespace std; #define ...
- bzoj5417/luoguP4770 [NOI2018]你的名字(后缀自动机+线段树合并)
bzoj5417/luoguP4770 [NOI2018]你的名字(后缀自动机+线段树合并) bzoj Luogu 给出一个字符串 $ S $ 及 $ q $ 次询问,每次询问一个字符串 $ T $ ...
- BZOJ3413: 匹配(后缀自动机 线段树合并)
题意 题目链接 Sol 神仙题Orz 后缀自动机 + 线段树合并... 首先可以转化一下模型(想不到qwq):问题可以转化为统计\(B\)中每个前缀在\(A\)中出现的次数.(画一画就出来了) 然后直 ...
- cf666E. Forensic Examination(广义后缀自动机 线段树合并)
题意 题目链接 Sol 神仙题Orz 后缀自动机 + 线段树合并 首先对所有的\(t_i\)建个广义后缀自动机,这样可以得到所有子串信息. 考虑把询问离线,然后把\(S\)拿到自动机上跑,同时维护一下 ...
- [Luogu5161]WD与数列(后缀数组/后缀自动机+线段树合并)
https://blog.csdn.net/WAautomaton/article/details/85057257 解法一:后缀数组 显然将原数组差分后答案就是所有不相交不相邻重复子串个数+n*(n ...
- 【BZOJ-4556】字符串 后缀数组+二分+主席树 / 后缀自动机+线段树合并+二分
4556: [Tjoi2016&Heoi2016]字符串 Time Limit: 20 Sec Memory Limit: 128 MBSubmit: 657 Solved: 274[Su ...
- [NOI2018]你的名字(后缀自动机+线段树合并)
看到题目名字去补番是种怎么样的体验 我只会 \(68\) 分,打了个暴力.正解看了一会儿,发现跟 \([HEOI2016/TJOI2016]\) 字符串很像,用线段树合并维护 \(endpos\) 集 ...
随机推荐
- sql ltrim/rtrim 字段中为中文时出现?的问题
字段存储为中文,类型为nvarchar,使用ltrim时结果集中出现的问号,我的解决办法是:将问号replace掉
- 原生JS获取地址了参数
方法一:采用正则表达式获取地址栏参数:( 强烈推荐,既实用又方便!) function GetQueryString(name) { var reg = new RegExp("( ...
- dbus通信与接口介绍
DBUS是一种高级的进程间通信机制.DBUS支持进程间一对一和多对多的对等通信,在多对多的通讯时,需要后台进程的角色去分转消息,当一个进程发消息给另外一个进程时,先发消息到后台进程,再通过后台进程将信 ...
- 1.struts 防止表单重复提交 2. 拦截器
1. 使用struts 防止表单提交 时, form 表单必须使用struts标签库编写,如<s:form/> 等,而不是html标签 2. 拦截器是struts2的核心. interc ...
- Socket使用大全
第一部分.概念的理解 1.什么是Socket? Socket又称之为“套接字”,是系统提供的用于网络通信的方法.它的实质并不是一种协议,没有规定计算机应当怎么样传递消息,只是给程序员提供了一个发送消息 ...
- 【Win】编写简单的bat文件
bat是dos下的批处理文件.批处理文件是无格式的文本文件,它包含一条或多条命令.它的文件扩展名为 .bat 或 .cmd.在命令提示下键入批处理文件的名称,或者双击该批处理文件,系统就会调用Cmd. ...
- 2018.07.03 POJ 2318 TOYS(二分+简单计算几何)
TOYS Time Limit: 2000MS Memory Limit: 65536K Description Calculate the number of toys that land in e ...
- arduino 与 android 通过TCP进行字节收发
arduino #include <avr/wdt.h> #include <SoftwareSerial.h> #define FPIN 13 SoftwareSerial ...
- js,java,jstl多分隔符分割字符串
1.js多分隔符 分割字符串 var username = “zhao,li;wang.liu”: var arr = str.split(/;|:|,|,|./); 括号里面可以写多分割符号,中英 ...
- modelsim读写TXT文件
//open the file Initial Begin step_file = $fopen("F:/Company/Src/txt/step.v","r" ...