Description

给出一个长为n的字符串\(S\)和一个长为n的序列\(a\)

定义一个函数\(f(l,r)\)表示子串\(S[l..r]\)的任意两个后缀的最长公共前缀的最大值。

现在有q组询问,每组询问给出\(L,R,x\)

你需要找到一个子串\(S[l,r]\)满足\([l,r]\subset[L,R]\)且\(f(l,r)\geq x\)

同时需要满足\(max(a[l..r])\)最小

求这个最小值,无解则输出-1

\(n,q\leq 50000\)

Solution

这道题实际上分成两部分(谴责拼题行为)

第一部分快速计算f(l,r)

建出SAM,用个set维护right集,在parent树上启发式合并,显然Right集合中我们只需要考虑哪些相邻的位置有用(不相邻显然不优),合并的时候把要插入的位置的前驱以及它们的LCP构成一个三元组,后继也一样处理,这样我们得到了一个可能更新答案的三元组序列\((l,x,r)\),表示\(S[l..x]=S[r-(x-l),r]\)。

观察对答案的贡献,它能贡献的区间满足\(R>r,l<=x\),相当于区间取max以及区间对等差数列取max

维护两棵线段树分别来弄,等差数列我们只需要维护合法右端点最大值即可,两个都是区间取max单点查询,可以用struct写在一起

还需要保证右端点的限制,因此对所有二元组按右端点排序建主席树即可。

区间修改的主席树似乎比较麻烦?我们发现这题不需要下传标记,因此直接先将当前位置的操作扔进去再继承前一个的。

这样就能够在\(O(n\log^2n)\)预处理(启发式合并),\(O(\log n)\)计算一个区间的f

第二部分计算答案

显然只有笛卡尔树上的n个极大区间是有用的,计算出它们的f

对于查询区间\([L,R]\),我们只需要找到被它完全包含的极大区间,然后分别以左右端点开始二分另一个端点即可。

找到包含的区间似乎是个二维问题?

不需要

对于左端点我们二分了一个位置,表示右端点在这左边的都不合法。

其他的合法区间的右端点一定在这右边,

此时我们不需要管左端点的限制了,因为超出去答案显然更劣。

直接一维区间最大值即可

这一部分也是\(O(n\log^2n)\)的

常数似乎比较大。

Code

代码足足有5K

略毒瘤。

#include <bits/stdc++.h>
#define fo(i,a,b) for(int i=a;i<=b;++i)
#define fod(i,a,b) for(int i=a;i>=b;--i)
const int N=50005;
using namespace std;
char ch[N];
int n,q;
struct node
{
int l,x,r;
friend bool operator <(node x,node y)
{
return x.r<y.r;
}
};
vector<node> ap; namespace SAM
{
const int R=N<<1;
int t[R][26],mx[R],m1,ft[R],fs[R],nt[R],dt[R],n2,rf[R];
set<int> ri[R];
void link(int x,int y)
{
nt[++m1]=fs[x];
dt[fs[x]=m1]=y;
}
void merge(int x,int y,int v)
{
if(v==0) return;
if(ri[rf[x]].size()<ri[rf[y]].size()) swap(rf[x],rf[y]);
for(int i:ri[rf[y]])
{
set<int>::iterator it=ri[rf[x]].lower_bound(i);
if(it!=ri[rf[x]].end()) ap.push_back((node){i-v+1,i,*it});
if(it!=ri[rf[x]].begin()) it--,ap.push_back((node){*it-v+1,*it,i});
ri[rf[x]].insert(i);
}
}
void dfs(int k)
{
int w=0;
for(int i=fs[k];i;i=nt[i])
{
int p=dt[i];
dfs(p);
merge(k,p,mx[k]);
}
}
void make()
{
n2=1;
int ls=n2;
fo(i,1,n)
{
int c=ch[i]-'a',p=ls;
mx[ls=++n2]=i;
rf[ls]=ls;
ri[ls].insert(i);
while(p&&!t[p][c]) t[p][c]=ls,p=ft[p];
if(t[p][c])
{
int q=t[p][c];
if(mx[q]==mx[p]+1) ft[ls]=q;
else
{
mx[++n2]=mx[p]+1;
ft[n2]=ft[q];
ft[q]=ft[ls]=n2;
memcpy(t[n2],t[q],sizeof(t[n2]));
while(p&&t[p][c]==q) t[p][c]=n2,p=ft[p];
}
}
else ft[ls]=1;
}
fo(i,2,n2) link(ft[i],i);
dfs(1);
}
} namespace QS
{
const int M=10000000; struct SGT
{
int t[M][2],mx[M],rt[N],n1;
inline int nwp(int &x)
{
if(!x) x=++n1,t[x][0]=t[x][1]=mx[x]=0;
return x;
}
void modify(int k,int l,int r,int x,int y,int v)
{
if(x>y) return;
if(x<=l&&r<=y) {mx[k]=max(mx[k],v);return;}
int mid=(l+r)>>1;
if(x<=mid) modify(nwp(t[k][0]),l,mid,x,y,v);
if(y>mid) modify(nwp(t[k][1]),mid+1,r,x,y,v);
}
void merge(int &k,int w,int l,int r)
{
if(!k) {k=w;return;}
if(!w) return;
mx[k]=max(mx[k],mx[w]);
int mid=(l+r)>>1;
merge(t[k][0],t[w][0],l,mid);
merge(t[k][1],t[w][1],mid+1,r);
}
int query(int k,int l,int r,int x)
{
if(l==r) return mx[k];
int mid=(l+r)>>1,s=0;
if(x<=mid) s=query(t[k][0],l,mid,x);
else s=query(t[k][1],mid+1,r,x);
return max(s,mx[k]);
}
}T1,T2;
void make()
{
SAM::make();
sort(ap.begin(),ap.end()); int r=ap.size()-1;
for(int i=1,j=0;i<=n;++i)
{
T1.rt[i]=++T1.n1;
T2.rt[i]=++T2.n1;
while(j<=r&&ap[j].r<=i)
{
T1.modify(T1.rt[i],1,n,1,ap[j].l-1,ap[j].x-ap[j].l+1);
T2.modify(T2.rt[i],1,n,ap[j].l,ap[j].x,ap[j].x);
j++;
}
T1.merge(T1.rt[i],T1.rt[i-1],1,n);
T2.merge(T2.rt[i],T2.rt[i-1],1,n);
}
n++;
n--;
}
int get(int l,int r)
{
return max(T1.query(T1.rt[r],1,n,l),min(T2.query(T2.rt[r],1,n,l),r)-l+1);
}
}
using QS::get; int a[N];
struct px
{
int l,r,v,c;
friend bool operator <(px x,px y) {return x.v>y.v;}
}aw[N]; int ask[N][3],d[N];
bool cmp(int x,int y) {return ask[x][2]>ask[y][2];}
int lf[N],rf[N],st[N],ans[N]; namespace Tr
{
int n1,t[N<<1][2],mx[N<<1],mi[N<<1];
void build(int k,int l,int r)
{
mi[k]=1e9;
if(l==r) {mx[k]=a[l];return;}
int mid=(l+r)>>1;
build(t[k][0]=++n1,l,mid),build(t[k][1]=++n1,mid+1,r);
mx[k]=max(mx[t[k][0]],mx[t[k][1]]);
}
void ins(int k,int l,int r,int x,int v)
{
if(l==r) {mi[k]=min(mi[k],v);return;}
int mid=(l+r)>>1;
if(x<=mid) ins(t[k][0],l,mid,x,v);
else ins(t[k][1],mid+1,r,x,v);
mi[k]=min(mi[t[k][0]],mi[t[k][1]]);
}
int ds[N];
void query(int k,int l,int r,int x,int y)
{
if(x>y) return;
if(x<=l&&r<=y) {ds[++ds[0]]=k;return;}
int mid=(l+r)>>1;
if(x<=mid) query(t[k][0],l,mid,x,y);
if(y>mid) query(t[k][1],mid+1,r,x,y);
}
int gmax(int l,int r)
{
ds[0]=0;
query(1,1,n,l,r);
int s=0;
fo(i,1,ds[0]) s=max(s,mx[ds[i]]);
return s;
}
int gmin(int l,int r)
{
ds[0]=0;
query(1,1,n,l,r);
int s=1e9;
fo(i,1,ds[0]) s=min(s,mi[ds[i]]);
return s;
}
}
using Tr::gmax;
using Tr::gmin;
int main()
{
cin>>n>>q;
scanf("\n%s",ch+1);
QS::make();
fo(i,1,n) scanf("%d",&a[i]); fo(i,1,n)
{
while(st[0]&&a[i]>a[st[st[0]]]) rf[st[st[0]]]=i-1,st[st[0]--]=0;
st[++st[0]]=i;
}
while(st[0]) rf[st[st[0]]]=n,st[st[0]--]=0; fod(i,n,1)
{
while(st[0]&&a[i]>a[st[st[0]]]) lf[st[st[0]]]=i+1,st[st[0]--]=0;
st[++st[0]]=i;
}
while(st[0]) lf[st[st[0]]]=1,st[st[0]--]=0; fo(i,1,n) aw[i]=(px){lf[i],rf[i],get(lf[i],rf[i]),a[i]};
sort(aw+1,aw+n+1); fo(i,1,q)
{
scanf("%d%d%d",&ask[i][0],&ask[i][1],&ask[i][2]);
d[i]=i;
}
sort(d+1,d+q+1,cmp);
int j=1;
Tr::n1=1;
Tr::build(1,1,n); fo(i,1,q)
{
while(j<=n&&aw[j].v>=ask[d[i]][2])
{
Tr::ins(1,1,n,aw[j].r,aw[j].c);
j++;
}
int l=ask[d[i]][0],r=ask[d[i]][1]+1;
while(l+1<r)
{
int mid=(l+r)>>1;
if(get(ask[d[i]][0],mid)>=ask[d[i]][2]) r=mid;
else l=mid;
}
int wp;
if(get(ask[d[i]][0],l)>=ask[d[i]][2]) wp=l;
else wp=r;
if(wp==ask[d[i]][1]+1) {ans[d[i]]=-1;continue;}
ans[d[i]]=min(gmax(ask[d[i]][0],wp),gmin(wp,ask[d[i]][1])); l=ask[d[i]][0],r=ask[d[i]][1];
while(l+1<r)
{
int mid=(l+r)>>1;
if(get(mid,ask[d[i]][1])>=ask[d[i]][2]) l=mid;
else r=mid;
}
if(get(r,ask[d[i]][1])>=ask[d[i]][2]) wp=r;
else wp=l;
ans[d[i]]=min(ans[d[i]],gmax(wp,ask[d[i]][1]));
}
fo(i,1,q) printf("%d\n",ans[i]);
}

[JZOJ6241]【NOI2019模拟2019.6.29】字符串【数据结构】【字符串】的更多相关文章

  1. 【NOI2019模拟2019.6.29】字符串(SA|SAM+主席树)

    Description: 1<=n<=5e4 题解: 考虑\(f\)这个东西应该是怎样算的? 不妨建出SA,然后按height从大到小启发式合并,显然只有相邻的才可能成为最优答案.这样的只 ...

  2. 【NOI2019模拟2019.6.29】组合数(Lucas定理、数位dp)

    Description: p<=10且p是质数,n<=7,l,r<=1e18 题解: Lucas定理: \(C_{n}^m=C_{n~mod~p}^{m~mod~p}*C_{n/p} ...

  3. [JZOJ6247]【NOI2019模拟2019.6.27】C【计数】

    Description n<=200000 Solution 比赛时没做出这道题真的太弟弟了 首先我们从小到大插入数i,考虑B中有多少个区间的最大值为i 恰好出现的次数不太好计算,我们考虑计算最 ...

  4. [JZOJ6244]【NOI2019模拟2019.7.1】islands【计数】【图论】

    Description n<=1e9,M,K<=100 Solution 显然任选m个港口的答案是一样的,乘个组合数即可. 考虑枚举m个港口的度数之和D 可以DP计算 记\(F_{m,D} ...

  5. [JZOJ6244]【NOI2019模拟2019.7.1】Trominoes 【计数】

    Description n,m<=10000 Solution 考虑暴力轮廓线DP,按顺序放骨牌 显然轮廓线长度为N+M 轮廓线也是单调的 1表示向上,0表示向右 N个1,M个0 只能放四种骨牌 ...

  6. 【NOI2019模拟2019.7.4】朝夕相处 (动态规划+BM)

    Description: 题解: 这种东西肯定是burnside引理: \(\sum置换后不动点数 \over |置换数|\) 一般来说,是枚举置换\(i\),则\(对所有x,满足a[x+i]=a[i ...

  7. 【NOI2019模拟2019.6.27】B (生成函数+整数划分dp|多项式exp)

    Description: \(1<=n,k<=1e5,mod~1e9+7\) 题解: 考虑最经典的排列dp,每次插入第\(i\)大的数,那么可以增加的逆序对个数是\(0-i-1\). 不难 ...

  8. 【NOI2019模拟2019.7.1】为了部落 (生成森林计数,动态规划)

    Description: \(1<=n<=1e9,1<=m,k<=100\) 模数不是质数. 题解: 先选m个点,最后答案乘上\(C_{n}^m\). 不妨枚举m个点的度数和D ...

  9. 【NOI2019模拟2019.7.1】三格骨牌(轮廓线dp转杨图上钩子定理)

    Description \(n,m<=1e4,mod ~1e9+7\) 题解: 显然右边那个图形只有旋转90°和270°后才能放置. 先考虑一个暴力的轮廓线dp: 假设已经放了编号前i的骨牌,那 ...

随机推荐

  1. Java中的mutable和immutable对象实例讲解

    1.mutable(可变)和immutable(不可变)类型的区别 可变类型的对象:提供了可以改变其内部数据值的操作,其内部的值可以被重新更改. 不可变数据类型:其内部的操作不会改变内部的值,一旦试图 ...

  2. Hive 教程(八)-hiveserver2

    hive 的另外一种启动方式是 hiveserver2,它是提供了一种服务,使得我们可以远程操作 hive,就像操作 mysql 一样 hiveserver1 既然有 hiveserver2,肯定有 ...

  3. JAVA基础:Java中equals和==的区别

    java中的数据类型,可分为两类:  1.基本数据类型,也称原始数据类型.byte,short,char,int,long,float,double,boolean    他们之间的比较,应用双等号( ...

  4. 使用Python基于OpenCV和Tesseract的OCR

    OCR OCR(Optical Character Recognition,光学字符识别)是指电子设备(例如扫描仪或数码相机)检查纸上打印的字符,通过检测暗.亮的模式确定其形状,然后用字符识别方法将形 ...

  5. colaui基础

    监控 c-watch // 监控的方法函数 on 监控的参数名字 div(c-watch="fun on style" c-bind="styles") // ...

  6. Visual Studio 2010中的js注释

    Visual Studio 2010中的js注释已经很强大了,但怎么才能和调用c#的方法一样容易呢?怎样才能让每个参数都有注释说明呢?底下就是想要的答案. 先上图,如图所示: 其中红色的办法为注释效果 ...

  7. IDEA + SpringBoot + maven 项目文件说明

    Springboot + maven + IDEA + git 项目文件介绍 1..gitignore  分布式版本控制系统git的配置文件,意思为忽略提交 在 .gitingore 文件中,遵循相应 ...

  8. Nginx请求限制配置

    Nginx请求限制配置 请求限制可以通过两种方式来配置,分别是  连接频率限制和请求频率限制 首先我们要知道什么是http请求和连接,浏览器和服务端首先通过三次握手完成连接,然后发起请求,传输请求参数 ...

  9. scala下划线的作用

    https://stackoverflow.com/questions/8000903/what-are-all-the-uses-of-an-underscore-in-scala Existent ...

  10. MongoDB系列(三):增删改查(CURD)

    上篇讲了MongoDB的基础知识,大家应该对MongoDB有所了解了,当然真正用的还是curd操作,本篇为大家讲解MongoDB的curd操作. 1.数据库操作 #.增 use config #如果数 ...