当前位置: 首页 > news >正文

洛谷P2042 [NOI2005] 维护数列

洛谷P2042 [NOI2005] 维护数列

洛谷题目传送门

题目描述

请写一个程序,要求维护一个数列,支持以下 666 种操作:

编号名称格式说明
1插入INSERT⁡positotc1c2⋯ctot\operatorname{INSERT}\ posi \ tot \ c_1 \ c_2 \cdots c_{tot}INSERT posi tot c1 c2ctot在当前数列的第 posiposiposi 个数字后插入 tottottot 个数字:c1,c2⋯ctotc_1, c_2 \cdots c_{tot}c1,c2ctot;若在数列首插入,则 posiposiposi000
2删除DELETE⁡positot\operatorname{DELETE} \ posi \ totDELETE posi tot从当前数列的第 posiposiposi 个数字开始连续删除 tottottot 个数字
3修改MAKE-SAME⁡positotc\operatorname{MAKE-SAME} \ posi \ tot \ cMAKE-SAME posi tot c从当前数列的第 posiposiposi 个数字开始的连续 tottottot 个数字统一修改为 ccc
4翻转REVERSE⁡positot\operatorname{REVERSE} \ posi \ totREVERSE posi tot取出从当前数列的第 posiposiposi 个数字开始的 tottottot 个数字,翻转后放入原来的位置
5求和GET-SUM⁡positot\operatorname{GET-SUM} \ posi \ totGET-SUM posi tot计算从当前数列的第 posiposiposi 个数字开始的 tottottot 个数字的和并输出
6求最大子列和MAX-SUM⁡\operatorname{MAX-SUM}MAX-SUM求出当前数列中和最大的一段子列,并输出最大和

输入格式

第一行包含两个整数 NNNMMMNNN 表示初始时数列中数的个数,MMM 表示要进行的操作数目。

第二行包含 NNN 个数字,描述初始时的数列。以下 MMM 行,每行一条命令,格式参见问题描述中的表格。

输出格式

对于输入数据中的 GET-SUM⁡\operatorname{GET-SUM}GET-SUMMAX-SUM⁡\operatorname{MAX-SUM}MAX-SUM 操作,向输出文件依次打印结果,每个答案(数字)占一行。

输入输出样例 #1

输入 #1

9 8 
2 -6 3 5 1 -5 -3 6 3 
GET-SUM 5 4
MAX-SUM
INSERT 8 3 -5 7 2
DELETE 12 1
MAKE-SAME 3 3 2
REVERSE 3 6
GET-SUM 5 4
MAX-SUM

输出 #1

-1
10
1
10

说明/提示

数据规模与约定
  • 你可以认为在任何时刻,数列中至少有 111 个数。
  • 输入数据一定是正确的,即指定位置的数在数列中一定存在。
  • 对于 50%50\%50% 的数据,任何时刻数列中最多含有 3×1043 \times 10^43×104 个数。
  • 对于 100%100\%100% 的数据,任何时刻数列中最多含有 5×1055 \times 10^55×105 个数,任何时刻数列中任何一个数字均在 [−103,103][-10^3, 10^3][103,103] 内,1≤M≤2×1041 \le M \le 2 \times 10^41M2×104,插入的数字总数不超过 4×1064 \times 10^64×106

题面由 @syksykCCC 提供。

思路详解

确定算法

根据我们的目测,这显然是一个裸数据结构的题,由于他要区间翻转,我们很容易就想到了要使用平衡树,不会区间翻转的可以去看这道题。在这里我采用的是无旋平衡树(FHQ)。下面我将为你相信讲解这道题。

一切的根基

由于我采用的是指针,我们首先要定义好结构体,并做好安全访问的每一个元素的函数,具体如下:

struct lit{int val,sum,pri,siz;
//值,区间和,优先级,大小int pre,suf,sub;
//最大前缀,最大后缀,最大子序列int co,rev;
//染色,翻转的懒标记lit *l,*r;
//左右子节点lit(int v){//构造val=sum=sub=v;//由于要求必须有一个元素,所以sub=v(后面会解释pre与suf)pri=rand();siz=1;pre=suf=max(0,v);co=-0x3f3f3f3f;rev=0;l=r=nullptr;}
};
//............................
int getsum(lit *u){return u?u->sum:0;}//安全访问
int getsize(lit *u){return u?u->siz:0;}
int getsub(lit *u){return u?u->sub:-0x3f3f3f3f;}
int getpre(lit *u){return u?u->pre:0;}
int getsuf(lit *u){return u?u->suf:0;}

打标记,更新与下传

我们要进行两种标记:

  1. 区间染色:染色了再翻转就没意义了,我们可以进行一下优化,染色后sum,pre,sub,suf,valsum,pre,sub,suf,valsum,pre,sub,suf,val都要改变,与构造差不多。
  2. 区间翻转:翻转首先要交换左右子树,然后,pre,sufpre,sufpre,suf也要交换。
void mark_co(lit *&u,int v){//区间染色if(!u)return;u->co=v;//更新标记u->sum=u->siz*v;//更新区间和u->val=v;//更新单点的值u->pre=u->suf=max(u->sum,0);//更新pre,suf,同上u->sub=max(v,u->sum);//sub至少一个元素,所以要么只取一个,要么全部取u->rev=0;//不需要翻转了}void mark_rev(lit *&u){//区间翻转if(!u)return;u->rev^=1;//更新标记swap(u->l,u->r);//交换左右子树swap(u->pre,u->suf);//交换pre,suf}

标记下传很简单,依次下传2个标记即可。

void pushdown(lit *&u){if(!u)return ;if(u->co!=-0x3f3f3f3f){//下传染色标记mark_co(u->l,u->co);mark_co(u->r,u->co);u->co=-0x3f3f3f3f;}if(u->rev){//下传翻转标记mark_rev(u->l);mark_rev(u->r);u->rev=0;}}

下面,到了本题比较难的部分了,那就是更新。更新的其他的值都没什么好说的,但我们要思考如何更新pre,suf,subpre,suf,subpre,suf,sub。我会在下面代码中详细讲解:

void pushup(lit *&u){if(!u)return;u->siz=1+getsize(u->l)+getsize(u->r);u->sum=getsum(u->l)+u->val+getsum(u->r);u->pre=max(max(getpre(u->l),getsum(u->l)+u->val+getpre(u->r)),0);
//最大前缀可能包含当前节点,也可能不。
//不包括时为getpre(u->l),包括时为getsum(u->l)+u->val+getpre(u->r)。u->suf=max(max(getsuf(u->r),getsum(u->r)+u->val+getsuf(u->l)),0);//同上u->sub=max(getsuf(u->l)+getpre(u->r)+u->val,max(getsub(u->l),getsub(u->r)));
//同理sub也可以包括或不包括,不包括时为max(getsub(u->l),getsub(u->r)),包括时为getsuf(u->l)+getpre(u->r)+u->val
}

FHQ核心

FHQ核心只有2个函数,分别为splitsplitsplit(分裂)与mergemergemerge(合并)。

void split(lit *u,int k,lit *&x,lit *&y){//分类if(!u){x=y=nullptr;return;}pushdown(u);//记得下传!!!int ls=getsize(u->l);if(ls+1<=k){x=u;split(u->r,k-ls-1,x->r,y);pushup(x);}else {y=u;split(u->l,k,x,y->l);pushup(y);}
}
lit *merge(lit *x,lit *y){if(!x)return y;if(!y)return x;pushdown(x);pushdown(y);//记得下传!!!if(x->pri>y->pri){x->r=merge(x->r,y);pushup(x);return x;}else{y->l=merge(x,y->l);pushup(y);return y;}
}

快速建点与删除

为了更好的完成此题,我们还要完成两个操作:

  1. 由于他要直接插入一个区间,所以我们要想办法快速生成这个区间,再合并即可。考虑同线段树一样二分处理。这样对mergemergemerge的调用只有log(n)log(n)log(n)次。
  2. 为了释放空间,我们要将一个区间彻底删除。
lit *add(int l,int r){//二分建树if(l==r)return new lit(t[l]);//边界int mid=(l+r)/2;return merge(add(l,mid),add(mid+1,r));}void clear(lit *&u){//递归删除if(!u)return ;clear(u->l);clear(u->r);delete u;}

封装

封装好每一个操作,我们的这道题就差不多了。

void insert(int p,int n){//加入一个区间lit *x,*y;split(root,p,x,y);root=merge(merge(x,add(1,n)),y);
}
void remove(int le,int ri){//删除区间[le,ri]lit *l,*mid,*r;split(root,ri,mid,r);split(mid,le-1,l,mid);if(mid)clear(mid);root=merge(l,r);
}
void change_color(int le,int ri,int v){//区间染色lit *l,*mid,*r;split(root,ri,mid,r);split(mid,le-1,l,mid);mark_co(mid,v);root=merge(merge(l,mid),r);
}
void change_reverse(int le,int ri){//区间翻转lit *l,*mid,*r;split(root,ri,mid,r);split(mid,le-1,l,mid);mark_rev(mid);root=merge(merge(l,mid),r);
}
int query_sum(int le,int ri){//求区间和lit *l,*mid,*r;split(root,ri,mid,r);split(mid,le-1,l,mid);int res=getsum(mid);root=merge(l,merge(mid,r));return res;
}
int query_sub(){//求整个区间的最大子序列return getsub(root); 
}

code

完整代码如下:

#include<bits/stdc++.h>
using namespace std;
const int N=4e6+5,INF=-0x3f3f3f3f;
struct lit{int val,sum,pri,siz;
//值,区间和,优先级,大小int pre,suf,sub;
//最大前缀,最大后缀,最大子序列int co,rev;
//染色,翻转的懒标记lit *l,*r;
//左右子节点lit(int v){//构造val=sum=sub=v;//由于要求必须有一个元素,所以sub=v(后面会解释pre与suf)pri=rand();siz=1;pre=suf=max(0,v);co=-0x3f3f3f3f;rev=0;l=r=nullptr;}
};
int t[N],n,m;
class FHQ_Treap{
private:lit *root;int getsum(lit *u){return u?u->sum:0;}//安全访问int getsize(lit *u){return u?u->siz:0;}int getsub(lit *u){return u?u->sub:-0x3f3f3f3f;}int getpre(lit *u){return u?u->pre:0;}int getsuf(lit *u){return u?u->suf:0;}void mark_co(lit *&u,int v){//区间染色if(!u)return;u->co=v;//更新标记u->sum=u->siz*v;//更新区间和u->val=v;//更新单点的值u->pre=u->suf=max(u->sum,0);//更新pre,suf,同上u->sub=max(v,u->sum);//sub至少一个元素,所以要么只取一个,要么全部取u->rev=0;//不需要翻转了}void mark_rev(lit *&u){//区间翻转if(!u)return;u->rev^=1;//更新标记swap(u->l,u->r);//交换左右子树swap(u->pre,u->suf);//交换pre,suf}void pushdown(lit *&u){if(!u)return ;if(u->co!=-0x3f3f3f3f){//下传染色标记mark_co(u->l,u->co);mark_co(u->r,u->co);u->co=-0x3f3f3f3f;}if(u->rev){//下传翻转标记mark_rev(u->l);mark_rev(u->r);u->rev=0;}}void pushup(lit *&u){if(!u)return;u->siz=1+getsize(u->l)+getsize(u->r);u->sum=getsum(u->l)+u->val+getsum(u->r);u->pre=max(max(getpre(u->l),getsum(u->l)+u->val+getpre(u->r)),0);
//最大前缀可能包含当前节点,也可能不。
//不包括时为getpre(u->l),包括时为getsum(u->l)+u->val+getpre(u->r)。u->suf=max(max(getsuf(u->r),getsum(u->r)+u->val+getsuf(u->l)),0);//同上u->sub=max(getsuf(u->l)+getpre(u->r)+u->val,max(getsub(u->l),getsub(u->r)));
//同理sub也可以包括或不包括,不包括时为max(getsub(u->l),getsub(u->r)),包括时为getsuf(u->l)+getpre(u->r)+u->val}void split(lit *u,int k,lit *&x,lit *&y){//分类if(!u){x=y=nullptr;return;}pushdown(u);//记得下传!!!int ls=getsize(u->l);if(ls+1<=k){x=u;split(u->r,k-ls-1,x->r,y);pushup(x);}else {y=u;split(u->l,k,x,y->l);pushup(y);}}lit *merge(lit *x,lit *y){if(!x)return y;if(!y)return x;pushdown(x);pushdown(y);//记得下传!!!if(x->pri>y->pri){x->r=merge(x->r,y);pushup(x);return x;}else{y->l=merge(x,y->l);pushup(y);return y;}}lit *add(int l,int r){//二分建树if(l==r)return new lit(t[l]);//边界int mid=(l+r)/2;return merge(add(l,mid),add(mid+1,r));}void clear(lit *&u){//递归删除if(!u)return ;clear(u->l);clear(u->r);delete u;}
public:void insert(int p,int n){//加入一个区间lit *x,*y;split(root,p,x,y);root=merge(merge(x,add(1,n)),y);}void remove(int le,int ri){//删除区间[le,ri]lit *l,*mid,*r;split(root,ri,mid,r);split(mid,le-1,l,mid);if(mid)clear(mid);root=merge(l,r);}void change_color(int le,int ri,int v){//区间染色lit *l,*mid,*r;split(root,ri,mid,r);split(mid,le-1,l,mid);mark_co(mid,v);root=merge(merge(l,mid),r);}void change_reverse(int le,int ri){//区间翻转lit *l,*mid,*r;split(root,ri,mid,r);split(mid,le-1,l,mid);mark_rev(mid);root=merge(merge(l,mid),r);}int query_sum(int le,int ri){//求区间和lit *l,*mid,*r;split(root,ri,mid,r);split(mid,le-1,l,mid);int res=getsum(mid);root=merge(l,merge(mid,r));return res;}int query_sub(){//求整个区间的最大子序列return getsub(root); }
}tr;
int main(){ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);cin>>n>>m;for(int i=1;i<=n;i++)cin>>t[i];tr.insert(0,n);for(int i=1;i<=m;i++){string s;int x,y,z;cin>>s;if(s=="INSERT"){cin>>x>>y;for(int j=1;j<=y;j++)cin>>t[j];tr.insert(x,y);}else if(s=="DELETE"){cin>>x>>y;tr.remove(x,x+y-1);}else if(s=="MAKE-SAME"){cin>>x>>y>>z;tr.change_color(x,x+y-1,z);}else if(s=="REVERSE"){cin>>x>>y;tr.change_reverse(x,x+y-1);}else if(s=="GET-SUM"){cin>>x>>y;cout<<tr.query_sum(x,x+y-1)<<'\n';}else{cout<<tr.query_sub()<<'\n';}}return 0;
}
http://www.dtcms.com/a/274862.html

相关文章:

  • 可以自定义皮肤的桌面备忘便签软件-滴哦小精灵 v1.4.5
  • 深入理解JVM
  • 视频翻译用什么软件?这里有5个高效推荐
  • 编码技术: PRBS, 8B/10B
  • MCU芯片内部的ECC安全机制
  • 提升你的AI交互技能:使用Anthropic互动提示教程
  • c语言中的数组IV
  • Qt:布局管理器Layout
  • flutter鸿蒙版 环境配置
  • Deekseek 学习笔记
  • 北京-4年功能测试2年空窗-报培训班学测开-第四十八天
  • 信创 CDC 实战 | TiDB 实时入仓难点与解决方案解析(以 ClickHouse 为例)
  • 【面板数据】省级泰尔指数及城乡收入差距测算(1990-2024年)
  • 大模型人类反馈强化学习RLHF 凭什么火出圈?人类反馈 + 强化学习,解锁 AI 行为可控密码
  • 盛世美颜伴杭州--花皙蔻牡丹盛世美颜精华油获选“2025杭州特色伴手礼”
  • 【Quest开发】快速添加可手指触摸按钮
  • unity VR linerenderer的线会被UI盖住
  • 微算法科技基于格密码的量子加密技术,融入LSQb算法的信息隐藏与传输过程中,实现抗量子攻击策略强化
  • 20250710-2-Kubernetes 集群部署、配置和验证-网络组件存在的意义?_笔记
  • 车载诊断进阶篇 --- 关于网关转发性能引起的思考
  • JAVA入门——安装java环境
  • 智能运维管理平台:AI赋能的数字化转型引擎
  • 从大模型到云游戏,国鑫SY8108G-G4如何化身“全能AI引擎”?
  • 挥别Feign,拥抱Spring 6.1 RestClient:高可用HTTP客户端构建之路
  • 雷达遥感星座微波射频组件抗辐照MCU的选型与实践
  • HarmonyOS基础概念
  • windows 装了 python2 和 python3 如何切换默认版本
  • 1.1.2 运算符与表达式——AI教你学Django
  • 常见的数集 N,Z,R,Q,C
  • osgb转fbx,osgb转su,osgb转obj,osgb转3dmax,osgb转glb,osgb转gltf,osgb转通用格式osgb转SKP