[HEOI2016/TJOI2016]字符串(后缀数组+二分+主席树/后缀自动机+倍增+线段树合并)
后缀数组解法:
先二分最长前缀长度 \(len\),然后从 \(rnk[c]\) 向左右二分 \(l\) 和 \(r\) 使 \([l,r]\) 的 \(height\geq len\),然后在主席树上查 \(sa[l..r]\) 是否有 \(a..b\) 中的任意一个数。时间复杂度 \(O(n\log^2 n)\)
\(Code\ Below:\)
#include <bits/stdc++.h>
using namespace std;
const int maxn=100000+10;
int n,m,sa[maxn],tax[maxn],rnk[maxn],tp[maxn],h[maxn],f[maxn][18],g[maxn][18];
int T[maxn],L[maxn*20],R[maxn*20],sum[maxn*20],cnt;char s[maxn];
inline int read(){
register int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
return (f==1)?x:-x;
}
void SA(int m){
int i,j,k,p,d=0;
for(i=1;i<=n;i++) rnk[i]=s[i];
for(i=1;i<=n;i++) tax[rnk[i]]++;
for(i=1;i<=m;i++) tax[i]+=tax[i-1];
for(i=n;i>=1;i--) sa[tax[rnk[i]]--]=i;
for(k=1,p=0;p<n;m=p,k<<=1){
p=0;
for(i=n-k+1;i<=n;i++) tp[++p]=i;
for(i=1;i<=n;i++) if(sa[i]>k) tp[++p]=sa[i]-k;
for(i=1;i<=m;i++) tax[i]=0;
for(i=1;i<=n;i++) tax[rnk[i]]++;
for(i=1;i<=m;i++) tax[i]+=tax[i-1];
for(i=n;i>=1;i--) sa[tax[rnk[tp[i]]]--]=tp[i];
swap(rnk,tp);rnk[sa[1]]=p=1;
for(i=2;i<=n;i++) rnk[sa[i]]=(tp[sa[i-1]]==tp[sa[i]]&&tp[sa[i-1]+k]==tp[sa[i]+k])?p:++p;
}
for(i=1;i<=n;i++) rnk[sa[i]]=i;
for(i=1;i<=n;i++){
p=sa[rnk[i]-1];if(d) d--;
while(s[i+d]==s[p+d]) d++;
h[rnk[i]]=d;
}
for(i=1;i<=n;i++) f[i][0]=h[i+1],g[i][0]=h[i];
for(j=1;j<=17;j++){
for(i=1;i+(1<<j)-1<=n;i++) f[i][j]=min(f[i][j-1],f[i+(1<<(j-1))][j-1]);
for(i=(1<<j);i<=n;i++) g[i][j]=min(g[i][j-1],g[i-(1<<(j-1))][j-1]);
}
}
void update(int &now,int pre,int l,int r,int x){
now=++cnt;L[now]=L[pre];R[now]=R[pre];sum[now]=sum[pre]+1;
if(l == r) return ;
int mid=(l+r)>>1;
if(x <= mid) update(L[now],L[pre],l,mid,x);
else update(R[now],R[pre],mid+1,r,x);
}
int query(int u,int v,int Le,int Ri,int l,int r){
if(Le <= l && r <= Ri) return sum[v]-sum[u];
int mid=(l+r)>>1,ans=0;
if(Le <= mid) ans+=query(L[u],L[v],Le,Ri,l,mid);
if(Ri > mid) ans+=query(R[u],R[v],Le,Ri,mid+1,r);
return ans;
}
int check(int len,int x,int a,int b){
int l=x,r=x;
for(int i=17;i>=0;i--)
if(l-(1<<i)>=1&&g[l][i]>=len) l-=1<<i;
for(int i=17;i>=0;i--)
if(r+(1<<i)<=n&&f[r][i]>=len) r+=1<<i;
int ans=query(T[l-1],T[r],a,b-len+1,1,n);
return ans;
}
int main()
{
n=read(),m=read();
scanf("%s",s+1);SA(128);
for(int i=1;i<=n;i++) update(T[i],T[i-1],1,n,sa[i]);
int a,b,c,d,l,r,mid,ans=0;
while(m--){
a=read(),b=read(),c=read(),d=read();
l=1;r=min(b-a+1,d-c+1);ans=0;
while(l<=r){
mid=(l+r)>>1;
if(check(mid,rnk[c],a,b)) l=mid+1,ans=mid;
else r=mid-1;
}
printf("%d\n",ans);
}
return 0;
}
后缀自动机解法:
因为后缀自动机只能处理后缀,所以我们将原串翻转一下,将最长前缀长度转化为最长后缀长度。然后我们对原串建一个后缀自动机,二分一下最长后缀长度 \(len\),从 \(pos[c]\) 出发倍增到 \(l[x]\geq len\) 的结点 \(x\),然后在 \(x\) 上查询 \(endpos\) 集合是否有 \(b+len-1..a\) 的任意一个数。维护 \(endpos\) 集合可以用线段树合并。
\(Code\ Below:\)
#include <bits/stdc++.h>
using namespace std;
const int maxn=500000+10;
int n,m,a[maxn],b[maxn],last,cnt,ch[maxn][26],fa[maxn],l[maxn];
int pos[maxn],f[maxn][21],T[maxn],L[maxn*60],R[maxn*60],sum[maxn*60],tot;
char s[maxn];
inline int read(){
register int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
return (f==1)?x:-x;
}
void insert(int c){
int p=last,q=++cnt;last=q;l[q]=l[p]+1;
for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=q;
if(!p) fa[q]=1;
else {
int r=ch[p][c];
if(l[p]+1==l[r]) fa[q]=r;
else {
int s=++cnt;l[s]=l[p]+1;
memcpy(ch[s],ch[r],sizeof(ch[r]));
fa[s]=fa[r];fa[r]=fa[q]=s;
for(;p&&ch[p][c]==r;p=fa[p]) ch[p][c]=s;
}
}
}
void pushup(int now){
sum[now]=sum[L[now]]+sum[R[now]];
}
void update(int &now,int l,int r,int x){
if(!now) now=++tot;
if(l == r){sum[now]++;return ;}
int mid=(l+r)>>1;
if(x <= mid) update(L[now],l,mid,x);
else update(R[now],mid+1,r,x);
pushup(now);
}
int merge(int x,int y,int l,int r){
if(!x||!y) return x+y;
if(l == r){sum[x]+=sum[y];return x;}
int mid=(l+r)>>1,z=++tot;
L[z]=merge(L[x],L[y],l,mid);
R[z]=merge(R[x],R[y],mid+1,r);
pushup(z);
return z;
}
int query(int now,int Le,int Ri,int l,int r){
if(!now) return 0;
if(Le <= l && r <= Ri) return sum[now];
int mid=(l+r)>>1,ans=0;
if(Le <= mid) ans+=query(L[now],Le,Ri,l,mid);
if(Ri > mid) ans+=query(R[now],Le,Ri,mid+1,r);
return ans;
}
int check(int len,int x,int L,int R){
for(int i=20;i>=0;i--)
if(f[x][i]&&l[f[x][i]]>=len) x=f[x][i];
return query(T[x],L+len-1,R,1,n);
}
int main()
{
n=read(),m=read();
scanf("%s",s+1);last=cnt=1;
reverse(s+1,s+n+1);
for(int i=1;i<=n;i++){
insert(s[i]-'a');pos[i]=last;
update(T[pos[i]],1,n,i);
}
for(int i=1;i<=cnt;i++) b[l[i]]++;
for(int i=1;i<=cnt;i++) b[i]+=b[i-1];
for(int i=1;i<=cnt;i++) a[b[l[i]]--]=i;
for(int i=cnt;i>=1;i--)
if(fa[a[i]]) T[fa[a[i]]]=merge(T[fa[a[i]]],T[a[i]],1,n);
for(int i=1;i<=cnt;i++) f[i][0]=fa[i];
for(int j=1;j<=20;j++)
for(int i=1;i<=cnt;i++) f[i][j]=f[f[i][j-1]][j-1];
int a,b,c,d,l,r,mid,ans;
while(m--){
a=n-read()+1,b=n-read()+1,c=n-read()+1,d=n-read()+1;
l=0,r=min(a-b+1,c-d+1),ans=0;
while(l<=r){
mid=(l+r)>>1;
if(check(mid,pos[c],b,a)) l=mid+1,ans=mid;
else r=mid-1;
}
printf("%d\n",ans);
}
return 0;
}
[HEOI2016/TJOI2016]字符串(后缀数组+二分+主席树/后缀自动机+倍增+线段树合并)的更多相关文章
- 【BZOJ-4556】字符串 后缀数组+二分+主席树 / 后缀自动机+线段树合并+二分
4556: [Tjoi2016&Heoi2016]字符串 Time Limit: 20 Sec Memory Limit: 128 MBSubmit: 657 Solved: 274[Su ...
- 【BZOJ4556】[Tjoi2016&Heoi2016]字符串 后缀数组+二分+主席树+RMQ
[BZOJ4556][Tjoi2016&Heoi2016]字符串 Description 佳媛姐姐过生日的时候,她的小伙伴从某东上买了一个生日礼物.生日礼物放在一个神奇的箱子中.箱子外边写了一 ...
- BZOJ4556 Tjoi2016&Heoi2016 字符串【后缀自动机+倍增+线段树合并】
Description 佳媛姐姐过生日的时候,她的小伙伴从某东上买了一个生日礼物.生日礼物放在一个神奇的箱子中.箱子外边写了 一个长为n的字符串s,和m个问题.佳媛姐姐必须正确回答这m个问题,才能打开 ...
- 【CF666E】Forensic Examination 广义后缀自动机+倍增+线段树合并
[CF666E]Forensic Examination 题意:给你一个字符串s和一个字符串集合$\{t_i\}$.有q个询问,每次给出$l,r,p_l,p_r$,问$s[p_l,p_r]$在$t_l ...
- BZOJ 3083 树链剖分+倍增+线段树
思路: 先随便选个点 链剖+线段树 1操作 就直接改root变量的值 2操作 线段树上改 3操作 分成三种情况 1.new root = xx 整个子树的min就是ans 2. lca(new roo ...
- HDU3710 Battle over Cities(最小生成树+树链剖分+倍增+线段树)
Battle over Cities Time Limit: 10000/5000 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Othe ...
- BZOJ 4556 [HEOI2016/TJOI2016]字符串
BZOJ 4556 [HEOI2016/TJOI2016]字符串 其实题解更多是用后缀数组+数据结构的做法,貌似也不好写. 反正才学了 sam 貌似比较简单的做法. 还是得先二分,然后倍增跳到 $ s ...
- 【bzoj4310】跳蚤 后缀数组+二分
题目描述 很久很久以前,森林里住着一群跳蚤.一天,跳蚤国王得到了一个神秘的字符串,它想进行研究. 首先,他会把串分成不超过 k 个子串,然后对于每个子串 S,他会从S的所有子串中选择字典序最大的那一个 ...
- 【BZOJ4556】字符串(后缀数组,主席树)
[BZOJ4556]字符串(后缀数组,主席树) 题面 BZOJ 题解 注意看题: 要求的是\([a,b]\)的子串和[c,d]的\(lcp\)的最大值 先来一下暴力吧 求出\(SA\)之后 暴力枚举\ ...
随机推荐
- SLI的相关学习
今天帮人安装前年的机皇-微星GT80S的操作系统,安装好后用鲁大师测试下跑分,发现双显卡和单显卡鲁大师的跑分竟然一样,就像副显卡根本没有工作,听主人所这台机器能跑到36万以上. 然后就苦逼的尝试,把B ...
- MySQL Server参数优化 - innodb_file_per_table(独立表空间)
1 简介 Innodb存储引擎可将所有数据存放于ibdata*的共享表空间,也可将每张表存放于独立的.ibd文件的独立表空间. 共享表空间以及独立表空间都是针对数据的存储方式而言的. ...
- 用python实现数学多元数学方程式计算
题目:公鸡5元钱一只,母鸡3元钱一只,小鸡3只一块钱,其中公鸡,母鸡,小鸡都必须有,问公鸡,母鸡,小鸡各买多少只刚好凑足100元钱? 一:数学算术分析: x+y+z=100 5x+3y+z/3=100 ...
- GUI的优化操作/添加背景图片等
一.背景图片的添加这是JAVA中添加背景图片的方式,基本思路先建立一个Label标签,然后建立一个层次的布局,将label标签添加到最下面去. ImageIcon image=new ImageIco ...
- 爬取数据时解析url时一直报错Caused by: java.net.URISyntaxException: Illegal character in query at index 823替换了所有空格和特殊字符还是无效
近日在用HttpClient访问抓取汇率时,为了省力,直接采用 String url = "http://api.liqwei.com/currency/?exchange=usd|cny& ...
- 牛客训练五:炫酷路途(c++与dp)
题目链接:传送门 思路:每隔2^i(0<=i<=INF)就有一条路径,所以可以将从头到尾的路线视为一个有向图, 将ai,bi以此输入,然后将路径从小到大排序,不断更新路径. __built ...
- openstack之flavor管理
概览 [root@cc07 ~]# nova help | grep flavor flavor-access-add Add flavor access for the given tenant. ...
- C++STL 预定义函数对象和函数适配器
预定义函数对象和函数适配器 预定义函数对象基本概念:标准模板库STL提前定义了很多预定义函数对象,#include <functional> 必须包含. 1使用预定义函数对象: void ...
- 微信小程序两种滑动方式
竖向滑动: <scroll-view scroll-y="true" style="height: 200rpx;"> <view style ...
- 行转列group_concat()函数妙用
测试环境准备: mysql> create table test01(name varchar(20),address varchar(20)); Query OK, 0 rows affect ...