[BZOJ4358]Permu(回滚莫队)

题面

给出一个长度为n的排列P(P1,P2,...Pn),以及m个询问。每次询问某个区间[l,r]中,最长的值域连续段长度。

分析

最简单的方法显然是用线段树维护最长值域连续段长度,复杂度\(O(n\sqrt n \log n)\),会TLE

我们以值为下标维护两个数组lb[v],rb[v]表示<v(定义为“左侧”)和>v(定义为“右侧)的连续段长度,当我们加入一个值v的时候,会产生一个长度为lb[v]+rb[v]+1的连续段,,可以用来更新答案。同时,我们需要更新lb,rb。对于v两端的连续段来说,我们不需要修改每一个值的lb,rb,只需要修改段边界的就可以了,因为下一次插入值w的时候,只有w落在某个连续段的边界上,我们才要更新答案,而更新的答案只与段边界的lb,rb有关

然后考虑如何莫队处理:

我们把询问以左端点块编号为第一关键字,右端点为第二关键字排序。对于一个块中的所有询问,我们发现r是递增的,所以不用撤销。而l不一定是递增的,我们要考虑如何撤销某个添加值的操作。

对于每一块,初始r设为块的右端点,然后每个询问先将r往右移,处理r和l不在同一块的询问。然后处理询问在块内之间的部分,直接暴力把询问的块内部分的所有点添加进去。修改前把原来lb,rb的值记下来,保存在栈里面。处理完这个询问之后再复原。

我们发现这样的时间复杂度和普通莫队是相同的,因为加入和撤销相当于左端点从l1移到l2,再移回来,因为在同一个块内,所以移动距离不会超过\(O(\sqrt n)\)

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define maxn 50000
#define maxm 50000
using namespace std;
inline void qread(int &x) {
x=0;
int sign=1;
char c=getchar();
while(c<'0'||c>'9') {
if(c=='-') sign=-1;
c=getchar();
}
while(c>='0'&&c<='9') {
x=x*10+c-'0';
c=getchar();
}
x=x*sign;
}
inline void qprint(int x) {
if(x<0) {
putchar('-');
qprint(-x);
} else if(x==0) {
putchar('0');
return;
} else {
if(x>=10) qprint(x/10);
putchar('0'+x%10);
}
}
int n,m;
int a[maxn+5];
int bsz;
int belong[maxn+5];
struct query{
int l;
int bel;//减少数组寻址次数
int r;
int id;
friend bool operator < (query p,query q){
return p.bel<q.bel||(p.bel==q.bel&&p.r<q.r);
}
}q[maxm+5]; int ans[maxn+5];
int rb[maxn+5],lb[maxn+5];
//记录第i向左和向右的连续段长度 //用于撤销修改操作的栈
struct node{
int type;//记录是哪个数组修改,1则是lb,2则是rb
int pos;
int val;
node(){ }
node(int _type,int _pos,int _val){
type=_type;
pos=_pos;
val=_val;
}
}st[maxn+5];
int main(){
qread(n);
qread(m);
for(int i=1;i<=n;i++) qread(a[i]);
bsz=n/sqrt(m);
for(int i=1;i<=n;i++) belong[i]=i/bsz+1;
for(int i=1;i<=m;i++){
qread(q[i].l);
qread(q[i].r);
q[i].id=i;
q[i].bel=belong[q[i].l];
}
sort(q+1,q+1+m);
int r=0;
int sum=0;
for(int i=1;i<=m;i++){
if(q[i].bel!=q[i-1].bel){//新的块
sum=0;
for(int i=1;i<=n;i++) lb[i]=rb[i]=0;
r=q[i].bel*bsz;
}
while(r<q[i].r){//如果l,r不在同一个块,把r在块外部分加入,r单调递增,可不用撤销修改
r++;
lb[a[r]]=lb[a[r]-1]+1;
rb[a[r]]=rb[a[r]+1]+1;
int tmp=lb[a[r]]+rb[a[r]]-1;
sum=max(sum,tmp);
//对于a[r]两端的连续段来说,我们不需要修改每一个值的lb,rb,只需要修改段边界的就可以了
//因为下一次插入a[r],若a[r]已经存在,则答案不变,
//若a[r]落在某个连续段的边界上,我们才要更新答案,而更新的答案只与段边界的lb,rb有关
lb[a[r]+rb[a[r]]-1]=tmp;
rb[a[r]-lb[a[r]]+1]=tmp;
}
int res=sum;//由于撤销对sum的修改比较麻烦,移动左端点的时候不更新sum,而更新答案res
int top=0;
//min(q[i].r,q[i].bel*bsz)表示把询问在当前块内部分加入
for(int l=q[i].l;l<=min(q[i].r,q[i].bel*bsz);l++){//移动左端点,要回滚
lb[a[l]]=lb[a[l]-1]+1;
rb[a[l]]=rb[a[l]+1]+1;
int tmp=lb[a[l]]+rb[a[l]]-1;
st[++top]=node(1,a[l]+rb[a[l]]-1,lb[a[l]+rb[a[l]]-1]);//修改前把原来的值记下来
st[++top]=node(2,a[l]-lb[a[l]]+1,rb[a[l]-lb[a[l]]+1]);
lb[a[l]+rb[a[l]]-1]=tmp;
rb[a[l]-lb[a[l]]+1]=tmp;
res=max(res,tmp);
}
for(int j=top;j>=1;j--){//撤销对连续段端点的修改
if(st[j].type==1) lb[st[j].pos]=st[j].val;
else rb[st[j].pos]=st[j].val;
}
for(int j=q[i].l;j<=min(q[i].r,q[i].bel*bsz);j++){//撤销新加入的点对lb,rb的修改
lb[a[j]]=rb[a[j]]=0;
}
ans[q[i].id]=res;
}
for(int i=1;i<=m;i++){
qprint(ans[i]);
putchar('\n');
}
}

[BZOJ4358]Permu(回滚莫队)的更多相关文章

  1. [bzoj4358]permu:莫队+线段树/回滚莫队

    这道题是几天前水过去的,现在快没印象了,水一发. 首先我们看到它让求解的是最长的值域 连续段长度,很好. 然后就想到了山海经,但但是我还没有做. 然后又想到了很久以前的一次考试的T3旅馆hotel(我 ...

  2. [CSP-S模拟测试]:ants(回滚莫队)

    题目描述 然而贪玩的$dirty$又开始了他的第三个游戏. $dirty$抓来了$n$只蚂蚁,并且赋予每只蚂蚁不同的编号,编号从$1$到$n$.最开始,它们按某个顺序排成一列.现在$dirty$想要进 ...

  3. LOJ.6504.[雅礼集训2018 Day5]Convex(回滚莫队)

    LOJ 莫队.发现只需要维护前驱后继就可以了. 但是加入一个点需要找到它当前的前驱后继,很麻烦还带个\(\log\). 但是如果只有删除某个点,只需要更新一下它的前驱后继即可. 用回滚莫队就好惹. 撤 ...

  4. BZOJ.4241.历史研究(回滚莫队 分块)

    题目链接 \(Description\) 长度为n的数列,m次询问,每次询问一段区间最大的 \(A_i*tm_i\) (重要度*出现次数) \(Solution\) 好像可以用莫队做,但是取max的操 ...

  5. 2018.09.26 bzoj5218: [Lydsy2017省队十连测]友好城市(回滚莫队)

    传送门 比较简单的一道回滚莫队吧. 每次询问用bitset优化kosaraju统计答案. 就是有点难调. 然后向dzyo学长学习了回滚莫队的一种简洁的实现方式,就是直接建立一个sqrt(m)∗sqrt ...

  6. 2018.08.14 bzoj4241: 历史研究(回滚莫队)

    传送们 简单的回滚莫队,调了半天发现排序的时候把m达成了n... 代码: #include<bits/stdc++.h> #define N 100005 #define ll long ...

  7. BZOJ4241:历史研究(回滚莫队)

    Description IOI国历史研究的第一人——JOI教授,最近获得了一份被认为是古代IOI国的住民写下的日记.JOI教授为了通过这份日记来研究古代IOI国的生活,开始着手调查日记中记载的事件. ...

  8. LOJ#6504. 「雅礼集训 2018 Day5」Convex(回滚莫队)

    题面 传送门 题解 因为并不强制在线,我们可以考虑莫队 然而莫队的时候有个问题,删除很简单,除去它和前驱后继的贡献即可.但是插入的话却要找到前驱后继再插入,非常麻烦 那么我们把它变成只删除的回滚莫队就 ...

  9. bzoj4241: 历史研究(回滚莫队)

    传送门 这是一个叫做回滚莫队的神奇玩意儿 是询问,而且不强制在线,就决定是你了莫队 如果是每次插入一个数是不是很简单? 然而悲剧的是我们莫队的时候不仅要插入数字还要删除数字 那么把它变成只插入不就行了 ...

随机推荐

  1. Springboot配置文件占位符

    一.配置文件占位符 1.application.properties server.port=8088 debug=false product.id=ID:${random.uuid} product ...

  2. 《SaltStack技术入门与实践》—— Mine

    Mine 本章节参考<SaltStack技术入门与实践>,感谢该书作者: 刘继伟.沈灿.赵舜东 Mine是SaltStack收集Minion数据存储到Master的一个组件,它的功能与Gr ...

  3. 尝试用了一哈wepy框架的感想

    恶心死我, 1 在项目里出现了中文乱码(utf-8在wpy文件里有中文和注释--编译后就转化成乱码, 把代码拷在另外的项目里,(该项目没有中文乱码现象,)编译出来就出现中文乱码, 然后我再在所拷的代码 ...

  4. Linux技术学习要点,您掌握了吗---初学者必看

    1.如何做好嵌入式Linux学习前的准备? 要成为一名合格的嵌入式Linux工程师,就需要系统的学习软.硬件相关领域内的知识,需要在最开始就掌握开发的规范和原则,养成良好的工作习惯.为了确保学习的效果 ...

  5. input el-input 只能输入正整数验证

    字母e在js中属于数字,所以一般的正则匹配 \d 是拦不住字母e 的 正确写法为: onKeypress="return (/[\d]/.test(String.fromCharCode(e ...

  6. shell中的=~的简单用法

    其中 ~ 其实是对后面的正则表达式表示匹配的意思,如果匹配就输出1, 不匹配就输出0 [[ $test =~ ^[0-9]+ ]] && echo 1 || echo 0

  7. ORACLE Physical Standby DG 之switch over

    DG架构图如下: 计划,切换之后的架构图: DG切换: 主备切换:这里所有的数据库数据文件.日志文件的路径是一致的 [旧主库]主库primarydb切换为备库standby3主库检查switchove ...

  8. Retina 屏幕与二倍图

    分辨率 屏幕分辨率:指屏幕可显示的像素的个数 图像分辨率:位图图像包含的像素的个数 对于 Retina 屏它的分辨率是传统屏的两倍,而屏幕大小没有变化,所以它需要的图片的分辨率应该是传统屏幕的两倍(甚 ...

  9. HTML-空格字符实体

      不换行空格,全称No-Break Space,它是最常见和我们使用最多的空格,大多数的人可能只接触了 ,它是按下space键产生的空格.在HTML中,如果你用空格键产生此空格,空格是不会累加的(只 ...

  10. WebSocket知识、轮询、长轮询、长连接

    一.WebSocket理论知识 1.什么是websocket WebSocket是HTML5新增的协议,它的目的是在浏览器和服务器之间建立一个不受限的双向通信的通道,比如说,服务器可以在任意时刻发送消 ...