到这里 \(A\) 了这题, \(Splay\) 就能算入好门了吧。

今天是个特殊的日子, \(NOI\) 出成绩, 大佬 \(Cu\)

不敢相信这一切这么快, 一下子机房就只剩我和 \(zrs\) 了。

忽然回想起之前大佬的一幕幕, 有一丝惆怅

真的不知道该怎么安慰dalao。。。

不过上天不会忽视那些默默努力的人的对吧

不想被说做作, 但是如果dalao能看到这篇博客的话,

大佬, 高考加油啊


为什么在这里写这些呢? \(Splay\) 其实是大佬领进门的, 学习的也是大佬的板子, 大佬很久以前的Q名还是 \(Splay\) 加上一颗椰子树。。放心吧大佬, 我会继续努力的


P2042 [NOI2005]维护数列

题目描述

请写一个程序,要求维护一个数列,支持以下 6 种操作:(请注意,格式栏 中的下划线‘ _ ’表示实际输入文件中的空格)

输入输出格式

输入格式:

输入文件的第 1 行包含两个数 N 和 M,N 表示初始时数列中数的个数,M 表示要进行的操作数目。 第 2 行包含 N 个数字,描述初始时的数列。 以下 M 行,每行一条命令,格式参见问题描述中的表格

输出格式:

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


\(Splay\) 的挺全面的基本操作: 插入、 删除、 修改、 翻转、 求和、 求最大子列

因为插入的数可能会很多, 题目又保证在任意时刻序列内元素的数量 \(<= 500 000\) 所以隐藏一个要求: 让我们回收节点编号

自然地, 有插入操作, 所以我们记得加入哨兵节点

\(Ins\) 插入

这里和原来的插入不同, 是一次插入一段序列, 容易想到现在主树外新建一棵树, 用树根代表整个序列, 像原来一样插入即可。

但是值得注意的是, 若是用原来单个元素插入的方法建树的话, 每次插入的复杂度可以达到 \(O(n\log n)\) ,加上 \(Splay\) 算法本来就常数较大, 承受不起这样的方法, 于是我们引入一种类似线段树建树的递归建树。 和线段树有所不同的是,每个点代表自己本身的属性和子区间的属性, 所以建完的 \(Splay\) 树的大小为 \(N\) , 复杂度是线性的。

注意引用以保证节点编号的正确传递

最后将主树的 \(x, x + 1\) 分别 \(Splay\) 到根和根的右子节点, 右子节点的左子节点就是插入的位置, 把新树的根接上去即可

int ori[maxn];
void build(int &id, int F, int l, int r){//注意引用
if(l > r)return ;//因为下面的-1和+1,可能会出现区间错位的情况
int mid = (l + r) >> 1;
id = New(F, ori[mid]);
if(l == r)return ;
build(ch[id][0], id, l, mid - 1);//注意这里和线段树不一样,子区间是没有mid的
build(ch[id][1], id, mid + 1, r);
pushup(id);
}
void Ins(){
int x = RD(), tot = RD();
for(int i = 1;i <= tot;i++)ori[i] = RD();
int rt;build(rt, 0, 1, tot);
x = find(root, x + 1);//注意哨兵节点的加一
splay(x, 0, root);
x = find(root, size[ch[root][0]] + 2);
splay(x, root, root);
ch[ch[root][1]][0] = rt;
fa[rt] = ch[root][1];
pushup(ch[root][1]), pushup(root);
}

\(Del\ \& \ New\)

删除及节点回收

同样和之前的单个删除不一样, 这是删除一段区间 \([L, R]\)。 容易想到我们应该将 \(L - 1, R + 1\)分别 \(Splay\) 到根和右子节点, 右子节点的左子树即为操作树

直接删除很简单, 只需要将根的右子节点的左子节点设为 \(0\) 即可, 但是这题要求我们空间回收, 所以我们用 \(dfs\) 遍历摘下来的子树上的节点, 将节点加入一个队列中即可, 下次新建节点可以直接在此队列中取出编号重新使用

对于新建节点的 \(New\) 函数, 因为这个点之前可能被使用过, 所谓我们需要将其所有属性全部初始化, 再使用

int root, tot;
queue<int>Q;
int New(int F, int v){//包办一切编号重启,方便得一批
int now;
if(Q.empty())now = ++tot;
else now = Q.front(), Q.pop();
fa[now] = F; ch[now][0] = ch[now][1] = 0;
val[now] = v;
size[now] = 1; lazy[now] = -INF;rev[now] = 0;
sum[now] = maxx[now] = v;
lmax[now] = rmax[now] = max(0, v);
return now;
}
void dfs_del(int id){//深搜回收编号
Q.push(id);
if(ch[id][0])dfs_del(ch[id][0]);
if(ch[id][1])dfs_del(ch[id][1]);
}
void Del(){
int x = RD(), tot = RD();
x = find(root, x);//哨兵节点 + 1 - 1
splay(x, 0, root);
x = find(root, size[ch[root][0]] + tot + 2);
splay(x, root, root);
int del = ch[ch[root][1]][0];
dfs_del(del);
ch[ch[root][1]][0] = 0;
pushup(ch[root][1]), pushup(root);
}

\(Change\ \&\ Reverse\)

区间修改及区间翻转

和 \(Splay\) 模板题的翻转差不多, 都是套路, 将 \(L - 1, R + 1\) \(Splay\) 到根和根的右子节点, 根的右子节点的左子树即为修改区间, 打个懒标记。 注意这里的懒标记是给儿子使用的。还有就是修改与翻转的优先级的关系: 只要全部染色了翻不翻转都无所谓, 故修改懒标记的优先级修改染色 \(>\) 翻转,所以我们可以在下推染色标记时将翻转懒标记清空; 同理在翻转时, 当此区间有染色标记的时候, 我们也不需要翻转了

注意后面需要维护区间最大子段和, 需要维护一个区间的 取左端最大值 、取右端最大值 、 本区间子段最大值(参见小白逛公园), 从而更新。

所以我们区间翻转注意要先交换子区间的取左右端最大值, 再交换两个区间

void pushup(int id){
int lid = ch[id][0], rid = ch[id][1];
size[id] = size[lid] + size[rid] + 1;
sum[id] = sum[lid] + sum[rid] + val[id];
lmax[id] = max(lmax[lid], sum[lid] + val[id] + lmax[rid]);
rmax[id] = max(rmax[rid], sum[rid] + val[id] + rmax[lid]);
int MAX = max(maxx[lid], maxx[rid]);
maxx[id] = max(MAX, rmax[lid] + val[id] + lmax[rid]);
}
void pushdown(int id){//像线段树一样标记给儿子用
int lid = ch[id][0], rid = ch[id][1];
if(lazy[id] != -INF){
val[lid] = val[rid] = lazy[id];
lazy[lid] = lazy[rid] = lazy[id];
sum[lid] = size[lid] * lazy[id];
sum[rid] = size[rid] * lazy[id];
if(lazy[id] >= 0){
maxx[lid] = lmax[lid] = rmax[lid] = sum[lid];
maxx[rid] = lmax[rid] = rmax[rid] = sum[rid];
}
else{
lmax[lid] = rmax[lid] = 0, maxx[lid] = lazy[id];
lmax[rid] = rmax[rid] = 0; maxx[rid] = lazy[id];
}
lazy[id] = -INF; rev[id] = 0;
}
if(rev[id]){
rev[id] = 0;rev[lid] ^= 1, rev[rid] ^= 1;
swap(lmax[lid], rmax[lid]), swap(lmax[rid], rmax[rid]);
swap(ch[lid][0], ch[lid][1]);
swap(ch[rid][0], ch[rid][1]);
}
}
void Change(){
int x = RD(), tot = RD(), c = RD();
x = find(root, x);
splay(x, 0, root);
x = find(root, size[ch[root][0]] + tot + 2);
splay(x, root, root);
int change = ch[ch[root][1]][0];
val[change] = lazy[change] = c;
sum[change] = c * size[change];
if(c >= 0)maxx[change] = lmax[change] = rmax[change] = sum[change];
else maxx[change] = c, lmax[change] = rmax[change] = 0;
pushup(ch[root][1]), pushup(root);
}
void Reverse(){
int x = RD(), tot = RD();
x = find(root, x);
splay(x, 0, root);
x = find(root, size[ch[root][0]] + tot + 2);
splay(x, root, root);
int change = ch[ch[root][1]][0];
if(lazy[change] == -INF){
rev[change] ^= 1;
swap(lmax[change], rmax[change]);
swap(ch[change][0], ch[change][1]);
pushup(ch[root][1]), pushup(root);
}
}

\(Sum\ \&\ MSum\)

区间求和及最大子段和

\(Sum\) 都是套路, 不再赘述, \(MSum\) 更简单, 只要你前面维护得正确, 输出根的最大子段和的值即可

void Sum(){
int x = RD(), tot = RD();
x = find(root, x);
splay(x, 0, root);
x = find(root, size[ch[root][0]] + tot + 2);
splay(x, root, root);
printf("%d\n", sum[ch[ch[root][1]][0]]);
}
void MSum(){
printf("%d\n", maxx[root]);
}

至此, \(Splay\) 的大部分区间操作都已经被提到和总结了。谢谢大佬,谢谢

Code

#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
#include<algorithm>
#define LL long long
using namespace std;
int RD(){
int flag = 1, out = 0;char c = getchar();
while(c < '0' || c > '9'){if(c == '-')flag = -1;c = getchar();}
while(c >= '0' && c <= '9'){out = out * 10 + c - '0';c = getchar();}
return flag * out;
}
const int maxn = 1000019, INF = 1e9 + 19;
int num, na;
//splay, 需支持插入删除修改翻转求和最大子列
int ch[maxn][2], fa[maxn];
int val[maxn];
int size[maxn], lazy[maxn], rev[maxn];
int sum[maxn], maxx[maxn], lmax[maxn], rmax[maxn];
int root, tot;
queue<int>Q;
int New(int F, int v){//包办一切编号重启,方便得一批
int now;
if(Q.empty())now = ++tot;
else now = Q.front(), Q.pop();
fa[now] = F; ch[now][0] = ch[now][1] = 0;
val[now] = v;
size[now] = 1; lazy[now] = -INF;rev[now] = 0;
sum[now] = maxx[now] = v;
lmax[now] = rmax[now] = max(0, v);
return now;
}
void pushup(int id){
int lid = ch[id][0], rid = ch[id][1];
size[id] = size[lid] + size[rid] + 1;
sum[id] = sum[lid] + sum[rid] + val[id];
lmax[id] = max(lmax[lid], sum[lid] + val[id] + lmax[rid]);
rmax[id] = max(rmax[rid], sum[rid] + val[id] + rmax[lid]);
int MAX = max(maxx[lid], maxx[rid]);
maxx[id] = max(MAX, rmax[lid] + val[id] + lmax[rid]);
}
void pushdown(int id){//像线段树一样标记给儿子用
int lid = ch[id][0], rid = ch[id][1];
if(lazy[id] != -INF){
val[lid] = val[rid] = lazy[id];
lazy[lid] = lazy[rid] = lazy[id];
sum[lid] = size[lid] * lazy[id];
sum[rid] = size[rid] * lazy[id];
if(lazy[id] >= 0){
maxx[lid] = lmax[lid] = rmax[lid] = sum[lid];
maxx[rid] = lmax[rid] = rmax[rid] = sum[rid];
}
else{
lmax[lid] = rmax[lid] = 0, maxx[lid] = lazy[id];
lmax[rid] = rmax[rid] = 0; maxx[rid] = lazy[id];
}
lazy[id] = -INF; rev[id] = 0;
}
if(rev[id]){
rev[id] = 0;rev[lid] ^= 1, rev[rid] ^= 1;
swap(lmax[lid], rmax[lid]), swap(lmax[rid], rmax[rid]);
swap(ch[lid][0], ch[lid][1]);
swap(ch[rid][0], ch[rid][1]);
}
}
bool lor(int id){return ch[fa[id]][0] == id ? 0 : 1;}
void spin(int id){
int F = fa[id], d = lor(id);
fa[id] = fa[F];
if(fa[F])ch[fa[F]][lor(F)] = id;
fa[F] = id;
ch[F][d] = ch[id][d ^ 1];
if(ch[id][d ^ 1])fa[ch[id][d ^ 1]] = F;
ch[id][d ^ 1] = F;
pushup(F), pushup(id);
}
void splay(int id, int goal, int &rt){//rt为splay的主根
while(fa[id] != goal){
int F = fa[id];
pushdown(fa[F]), pushdown(F), pushup(id);
if(fa[F] == goal)spin(id);
else if(lor(id) ^ lor(F))spin(id), spin(id);
else spin(F), spin(id);
}
if(!goal)rt = id;
}
int find(int id, int rank){
pushdown(id);
if(!id)return INF;
if(size[ch[id][0]] >= rank)return find(ch[id][0], rank);
else if(size[ch[id][0]] + 1 == rank)return id;
else return find(ch[id][1], rank - size[ch[id][0]] - 1);
}
void insert(int id, int v, int &rt){
ch[id][1] = New(id, v);
splay(ch[id][1], 0, rt);
}
int ori[maxn];
void build(int &id, int F, int l, int r){//注意引用
if(l > r)return ;//因为下面的-1和+1,可能会出现区间错位的情况
int mid = (l + r) >> 1;
id = New(F, ori[mid]);
if(l == r)return ;
build(ch[id][0], id, l, mid - 1);//注意这里和线段树不一样,子区间是没有mid的
build(ch[id][1], id, mid + 1, r);
pushup(id);
}
void Ins(){
int x = RD(), tot = RD();
for(int i = 1;i <= tot;i++)ori[i] = RD();
int rt;build(rt, 0, 1, tot);
x = find(root, x + 1);//注意哨兵节点的加一
splay(x, 0, root);
x = find(root, size[ch[root][0]] + 2);
splay(x, root, root);
ch[ch[root][1]][0] = rt;
fa[rt] = ch[root][1];
pushup(ch[root][1]), pushup(root);
}
void dfs_del(int id){//深搜回收编号
Q.push(id);
if(ch[id][0])dfs_del(ch[id][0]);
if(ch[id][1])dfs_del(ch[id][1]);
}
void Del(){
int x = RD(), tot = RD();
x = find(root, x);//哨兵节点 + 1 - 1
splay(x, 0, root);
x = find(root, size[ch[root][0]] + tot + 2);
splay(x, root, root);
int del = ch[ch[root][1]][0];
dfs_del(del);
ch[ch[root][1]][0] = 0;
pushup(ch[root][1]), pushup(root);
}
void Change(){
int x = RD(), tot = RD(), c = RD();
x = find(root, x);
splay(x, 0, root);
x = find(root, size[ch[root][0]] + tot + 2);
splay(x, root, root);
int change = ch[ch[root][1]][0];
val[change] = lazy[change] = c;
sum[change] = c * size[change];
if(c >= 0)maxx[change] = lmax[change] = rmax[change] = sum[change];
else maxx[change] = c, lmax[change] = rmax[change] = 0;
pushup(ch[root][1]), pushup(root);
}
void Reverse(){
int x = RD(), tot = RD();
x = find(root, x);
splay(x, 0, root);
x = find(root, size[ch[root][0]] + tot + 2);
splay(x, root, root);
int change = ch[ch[root][1]][0];
if(lazy[change] == -INF){
rev[change] ^= 1;
swap(lmax[change], rmax[change]);
swap(ch[change][0], ch[change][1]);
pushup(ch[root][1]), pushup(root);
}
}
void Sum(){
int x = RD(), tot = RD();
x = find(root, x);
splay(x, 0, root);
x = find(root, size[ch[root][0]] + tot + 2);
splay(x, root, root);
printf("%d\n", sum[ch[ch[root][1]][0]]);
}
void MSum(){
printf("%d\n", maxx[root]);
}
int main(){
num = RD(), na = RD();
root = New(0, -INF);
for(int i = 1;i <= num;i++)insert(root, RD(), root);
insert(root, -INF, root);//初始化
char cmd[19];
for(int i = 1;i <= na;i++){
cin>>cmd;
if(cmd[2] == 'S')Ins();
else if(cmd[2] == 'L')Del();
else if(cmd[2] == 'K')Change();
else if(cmd[2] == 'V')Reverse();
else if(cmd[2] == 'T')Sum();
else MSum();
}
return 0;
}

P2042 [NOI2005]维护数列 && Splay区间操作(四)的更多相关文章

  1. 洛谷 P2042 [NOI2005]维护数列-Splay(插入 删除 修改 翻转 求和 最大的子序列)

    因为要讲座,随便写一下,等讲完有时间好好写一篇splay的博客. 先直接上题目然后贴代码,具体讲解都写代码里了. 参考的博客等的链接都贴代码里了,有空再好好写. P2042 [NOI2005]维护数列 ...

  2. P2042 [NOI2005]维护数列[splay或非旋treap·毒瘤题]

    P2042 [NOI2005]维护数列 数列区间和,最大子列和(必须不为空),支持翻转.修改值.插入删除. 练码力的题,很毒瘤.个人因为太菜了,对splay极其生疏,犯了大量错误,在此记录,望以后一定 ...

  3. BZOJ 1500 Luogu P2042 [NOI2005] 维护数列 (Splay)

    手动博客搬家: 本文发表于20180825 00:34:49, 原地址https://blog.csdn.net/suncongbo/article/details/82027387 题目链接: (l ...

  4. [bzoj1500][NOI2005 维修数列] (splay区间操作)

    Description Input 输入的第1 行包含两个数N 和M(M ≤20 000),N 表示初始时数列中数的个数,M表示要进行的操作数目. 第2行包含N个数字,描述初始时的数列. 以下M行,每 ...

  5. Luogu P2042 [NOI2005]维护数列(平衡树)

    P2042 [NOI2005]维护数列 题意 题目描述 请写一个程序,要求维护一个数列,支持以下\(6\)种操作:(请注意,格式栏中的下划线'_'表示实际输入文件中的空格) 输入输出格式 输入格式: ...

  6. [NOI2005]维护数列(区间splay)

    [NOI2005]维护数列(luogu) 打这玩意儿真是要了我的老命 Description 请写一个程序,要求维护一个数列,支持以下 6 种操作:(请注意,格式栏 中的下划线‘ _ ’表示实际输入文 ...

  7. BZOJ1500: [NOI2005]维修数列 [splay序列操作]【学习笔记】

    以前写过这道题了,但我把以前的内容删掉了,因为现在感觉没法看 重写! 题意: 维护一个数列,支持插入一段数,删除一段数,修改一段数,翻转一段数,查询区间和,区间最大子序列 splay序列操作裸题 需要 ...

  8. NOI2005 维护数列(splay)

    学了半天平衡树,选择了一道题来写一写,发现题目是裸的splay模板,但是还是写不好,这个的精髓之处在于在数列的某一个位置加入一个数列,类似于treap里面的merge,然后还学到了题解里面的的回收空间 ...

  9. Luogu P2042 [NOI2005]维护数列

    题目描述 请写一个程序,要求维护一个数列,支持以下 6 种操作:(请注意,格式栏 中的下划线' _ '表示实际输入文件中的空格) 输入输出格式 输入格式: 输入文件的第 1 行包含两个数 N 和 M, ...

随机推荐

  1. JAVA第二次试验

    北京电子科技学院(BESTI) 实     验    报     告 课程:Java程序设计 班级:1352  姓名:潘俊洋  学号:20135230 成绩:             指导教师:娄嘉鹏 ...

  2. 第二阶段Sprint6

    昨天:设置统一保存路径为内存卡,实现可以选择播放已有的视频 今天:将“录制”及“保存”整合到一起,修复出现的Bug,使之能够正常运行. 遇到的问题:感觉调的摄像头录制的画面不好,这怎么办啊?

  3. 找"1"

    题目:给定一个十进制的正整数,写下从1开始,到N的所有整数,然后数一下其中出现“1”的次数. 要求:1.写一个函数f(N),返回1到N之间出现“1”的个数.例如f(12)=5. 2.在32位整数范围内 ...

  4. CAS (1) —— Mac下配置CAS到Tomcat(服务端)

    CAS (1) -- Mac下配置CAS到Tomcat(服务端) tomcat版本: tomcat-8.0.29 jdk版本: jdk1.8.0_65 cas版本: cas4.1.2 cas-clie ...

  5. 正确理解 SqlConnection 的连接池机制[转]

    作者: eaglet 转载请注明出处 .net 中通过 SqlConnection 连接 sql server,我们会发现第一次连接时总是很耗时,但后面连接就很快,这个其实和SqlConnection ...

  6. 【Java线程】SwingWorker的用法

    Swing应用程序员常见的错误是误用Swing事件调度线程(Event DispatchThread,EDT).他们要么从非UI线程访问UI组件:要么不考虑事件执行顺序:要么不使用独立任务线程而在ED ...

  7. NOI前训练日记

    向别人学习一波,记点流水帐.17.5.29开坑. 5.29 早晨看了道据说是树状数组优化DP的题(hdu5542),然后脑补了一个复杂度500^3的meet in the middle.然后死T... ...

  8. 【Java并发编程】之十:使用wait/notify/notifyAll实现线程间通信的几点重要说明

    在Java中,可以通过配合调用Object对象的wait()方法和notify()方法或notifyAll()方法来实现线程间的通信.在线程中调用wait()方法,将阻塞等待其他线程的通知(其他线程调 ...

  9. ubuntu在终端使用的常用命令

    1.ubuntu系统显示IP地址:ifconfig 2.ubuntu系统文件命令: cat:显示文本文件内容,全部文本.格式:cat filename more:显示文件内容,分页显示,回车逐行下翻. ...

  10. 数据结构开发(10):Linux内核链表

    0.目录 1.老生常谈的两个宏(Linux) 1.1 offsetof 1.2 container_of 2.Linux内核链表剖析 3.小结 1.老生常谈的两个宏(Linux) Linux 内核中常 ...