我只是来填坑的。

关于权值线段树的更多有关内容见此。

题面

题目描述

众所周知, GM \texttt{GM} GM 家的狗特别喜欢拉便便。 GM \texttt{GM} GM 为了方便它方便,在家里修建了 1 0 9 10^9 109 个马桶,依次排开,成一条直线,为了方便,依次编号 1 1 1 到 1 0 9 10^9 109 。

GM \texttt{GM} GM 家的狗叫“地铺雀儿”,“地铺雀儿”每次会选择一个马桶方便,但是很不幸,它不会冲厕所。 GM \texttt{GM} GM 为了冲厕所方便,修建了一个巨型水桶,可以一次冲掉一个区间内的每个厕所。(当然,区间上如果有没用过的厕所,也会一起冲,这样也许有些浪费水)

“地铺雀儿”还有一个特殊的爱好,它想观察某个区间上所有便便排序后的第 k k k 大便便在哪个厕所中,以方便跟它朋友吹嘘自己多能能拉便便。比如它观察区间 2 ∼ 10 2\sim 10 2∼10 中,想找出有便便的编号第 2 2 2 大的厕所,有 2 , 4 , 5 , 6 2,4,5,6 2,4,5,6 四个厕所各有一坨便便,那么答案就是5。

注意:“地铺雀儿”不怎么讲卫生,如果某厕所有便便了,它还可以继续在这个厕所拉便便。(也就是这个厕所里有多个便便),这些便便也要参加排序。

给你 q q q 个操作:

操作 1 1 1 的格式是:1 x,表示“地铺雀儿”在x号位置拉便便;

操作 2 2 2 的格式是:2 l r,表示 GM \texttt{GM} GM 冲掉区间 [ l , r ] [l,r] [l,r] 之间的所有便便,如果一个厕所有多个便便也一并冲走。

操作 3 3 3 的格式是:3 l r k,表示“地铺雀儿”想在区间l到r的范围内,包括 l l l 和 r r r ,寻找第 k k k 大的厕所编号(若不存在输出-1)

输入格式

第一行输入一个 q q q,代表询问次数 ( q ⩽ 1 0 5 ) (q\leqslant10^5) (q⩽105) 接下来 q q q 行,每行先输入一个 o p op op,如果op==1,则只输入一个 x x x ;若op==2,则输入 l , r l,r l,r ;若op==3,则需要输入 l , r , k l,r,k l,r,k。

输出格式

输出op==3时的所有询问

样例

样例输入

20
1 3
1 2
1 1
1 1
1 2
1 3
1 2
1 3
3 1 3 1
3 1 3 2
3 1 3 3
3 1 3 4
3 1 3 5
3 1 3 6
3 1 3 7
3 1 3 8
3 1 2 1
3 1 2 2
3 1 2 3
3 1 2 4

样例输出

3
3
3
2
2
2
1
1
2
2
2
1

数据范围与提示

Solution

总结一下题意:

有一些带权值的 shit 和 q q q 个操作 ( 1 ⩽ q ⩽ 1 0 5 ) (1\leqslant q\leqslant 10^5) (1⩽q⩽105) ,操作分为三种:

  • 操作一:输入 x x x ,将权值为 x x x 的 shit 的数量增加 1 1 1 (权值线段树动态开点中的Insert
  • 操作二:输入 l , r l,r l,r ,将权值在 [ l , r ] [l,r] [l,r] 范围内的 shit 的数量清空 (区间修改中的Update
  • 操作三:输入 l , r , k l,r,k l,r,k ,输出权值在 [ l , r ] [l,r] [l,r] 范围内,权值第 k k k 大的 shit 。(权值线段树中的Query

那么,我们发现,操作二是个区间修改,这样一来所有函数都需要Spread,所以我们先来解决操作二。

区间修改还是很好写的,首先我们先规定每一个结点存放的值:

int l,r,ll,rr;
//左儿子编号,右儿子编号,左端点,右端点
bool add;
//懒标记,标记当前值域是否被清空,因为清空只有一种固定的方式,所以将add定义为bool变量
int num;
//记录当前值域每个数的数量之和
//(不要问我为什么不写sum,因为我的sum写着写着就变成了num了)

那么Spread 就非常好打了:

void Spread(int p){if(a[p].add){int lt=a[p].l;//存左子树的编号int rt=a[p].r;//存右子树的编号//-----延伸-----a[lt].num=0;//清空a[lt].add=1;//懒标a[rt].num=0;//清空a[rt].add=1;//懒标//----清除-----a[p].add=0;//去懒标}return;
}

Spread都出来了,还愁什么Update

首先,打出一份动态开点区间修改的板子。

因为是动态开点,我们还是要处理一下当前值域是否存在的问题。

如果当前值域不存在,也就是递归到的结点编号为默认的 0 0 0 时,就可以return了。

因为当前结点不存在,说明之前根本没有被修改过,值域中每个数出现的次数都是 0 0 0 次,相当于已经是空的了,也就不用再多此一举,手动清空了。

void Update(int p,int l,int r){if(!p)return;if(l<=a[p].ll&&a[p].rr<=r){a[p].num=0;a[p].add=1;return;}Spread(p);int lt=a[p].l,rt=a[p].r;int mid=a[p].ll+a[p].rr>>1;if(l<=mid)Update(lt,l,r);if(r>mid)Update(rt,l,r);a[p].num=a[lt].num+a[rt].num;return;
}

氮素,区间修改的次数太多也是有可能T的,我们思考,怎么减少区间修改的次数?

当 p p p 所表示的值域已经被打过懒标记了,就说明这个值域之前已经被清空过了,并且其中的数的数量没有新增(否则Insert会调用Spread将 p p p 的懒标去掉)

如果这个值域中数出现的次数之和为 0 0 0 ,那么也不用清空了,本身就是空的嘛。

void Update(int p,int l,int r){//区间修改if(!p||a[p].add||!a[p].num)return;if(l<=a[p].ll&&a[p].rr<=r){a[p].num=0;a[p].add=1;return;}Spread(p);int lt=a[p].l,rt=a[p].r;int mid=a[p].ll+a[p].rr>>1;if(l<=mid)Update(lt,l,r);if(r>mid)Update(rt,l,r);a[p].num=a[lt].num+a[rt].num;return;
}

好的,那么接下来就是Insert

往正常的Insert里塞个Spread就行惹。

void Insert(int p,int l,int r,int x){//单点修改 a[p].ll=l,a[p].rr=r;if(l==r){a[p].add=0;a[p].num++;return;}int mid=l+r>>1;Spread(p);if(x<=mid){if(!a[p].l)a[p].l=++tot;Insert(a[p].l,l,mid,x);}else{if(!a[p].r)a[p].r=++tot;Insert(a[p].r,mid+1,r,x);}a[p].num++;return;
}

最后,是最难搞的Query

编号第 k k k 大,要怎么处理呢?

我们的权值线段树从左到右表示的数/值域是从小到大的,那么,更大的就在偏向右边的地方。(笔者表达能力不好,见谅)

如果我们要查询的区间 [ l , r ] [l,r] [l,r] 被右子树包含一部分,我们就看:


若①值域中包含的数的个数 ⩾ k \geqslant k ⩾k ,说明第 k k k 大数就在①值域中,递归询问右子树。

相应的,如果①值域中包含的数的个数不到 k k k ,说明第 k k k 大的数在左子树中,递归询问左子树。

注意,此时虽然右子树没有 k k k 那么多个数,但是也有自己的包含数的个数(假设为 r d a t rdat rdat 个),查询左子树时就不能再查第 k k k 大的了,而是 k − r d a t k-rdat k−rdat 大的数。

int QuerySum(int p,int l,int r){if(!p||a[p].add||!a[p].num)return 0;//理由同 Update,这些情况都是包含数的个数为0的if(l<=a[p].ll&&r>=a[p].rr)return a[p].num;int val=0;Spread(p);int lt=a[p].l,rt=a[p].r;int mid=a[p].ll+a[p].rr>>1;if(l<=mid)val+=QuerySum(lt,l,r);if(r>mid)val+=QuerySum(rt,l,r);return val;
}
int Query(int p,int l,int r,int k){if(!p||a[p].add||!a[p].num)return -1;//理由同 Update,如果一个数也没有,也就没有第k大数的说法if(a[p].ll==a[p].rr){if(a[p].num>=k)return a[p].ll;return -1;}Spread(p);int lt=a[p].l,rt=a[p].r;int rdat=0;//保存右子树的查询数据int mid=a[p].ll+a[p].rr>>1;if(r>mid){rdat=QuerySum(rt,l,r);if(k<=rdat)return Query(rt,l,r,k);}if(l<=mid)return Query(lt,l,r,k-rdat);//即使右子树没有包含询问区间,rdat也是0,不会对k产生影响return -1;
}

Code

坑坑洼洼的代码

#include<cmath>
#include<cstdio>
const int maxn=1e9;
const int maxm=1e5*log(1e9)/log(2);
#define int long long //经典lym行为,GM墙裂 不 推荐写法
struct Segment_tree{int l,r;bool add;int num,ll,rr;
}a[maxm];
int n,tot,type,l,r,x;
void Spread(int p){if(a[p].add){int lt=a[p].l;int rt=a[p].r;a[lt].num=0;a[lt].add=1;a[rt].num=0;a[rt].add=1;a[p].add=0;}return;
}
void Insert(int p,int l,int r,int x){//单点修改 a[p].ll=l,a[p].rr=r;if(l==r){a[p].add=0;a[p].num++;return;}int mid=l+r>>1;Spread(p);if(x<=mid){if(!a[p].l)a[p].l=++tot;Insert(a[p].l,l,mid,x);}else{if(!a[p].r)a[p].r=++tot;Insert(a[p].r,mid+1,r,x);}a[p].num++;return;
}
void Update(int p,int l,int r){//区间修改if(!p||a[p].add||!a[p].num)return;if(l<=a[p].ll&&a[p].rr<=r){a[p].num=0;a[p].add=1;return;}Spread(p);int lt=a[p].l,rt=a[p].r;int mid=a[p].ll+a[p].rr>>1;if(l<=mid)Update(lt,l,r);if(r>mid)Update(rt,l,r);a[p].num=a[lt].num+a[rt].num;return;
}
int QuerySum(int p,int l,int r){if(!p||a[p].add||!a[p].num)return 0;
//  printf("QuerySum->[%d,%d]|%d",a[p].ll,a[p].rr,a[p].num);if(l<=a[p].ll&&r>=a[p].rr)return a[p].num;int val=0;Spread(p);int lt=a[p].l,rt=a[p].r;int mid=a[p].ll+a[p].rr>>1;if(l<=mid)val+=QuerySum(lt,l,r);if(r>mid)val+=QuerySum(rt,l,r);return val;
}
int Query(int p,int l,int r,int k){if(!p||a[p].add||!a[p].num)return -1;if(a[p].ll==a[p].rr){if(a[p].num>=k)return a[p].ll;return -1;}Spread(p);int lt=a[p].l,rt=a[p].r;int rdat=0;int mid=a[p].ll+a[p].rr>>1;
//  printf("Query->[%d,%d]\n",a[p].ll,a[p].rr);if(r>mid){//      printf("[%d,%d]:right\n",a[p].ll,a[p].rr);rdat=QuerySum(rt,l,r);
//      printf("=%d\n",rdat);if(k<=rdat)return Query(rt,l,r,k);}if(l<=mid){//      printf("[%d,%d]:left\n",a[p].ll,a[p].rr);return Query(lt,l,r,k-rdat);}return -1;
}
signed main(){scanf("%lld",&n);++tot;for(int i=1;i<=n;++i){scanf("%lld",&type);if(type==1){scanf("%lld",&x);Insert(1,1,maxn,x);}else if(type==2){scanf("%lld%lld",&l,&r);Update(1,l,r);}else{scanf("%lld%lld%lld",&l,&r,&x);printf("%lld\n",Query(1,l,r,x));}}return 0;
}

end.

【题解】有便便的厕所(权值线段树动态开点模板题)相关推荐

  1. 权值线段树+动态开点(学习小结)

    首先先上一道板题:把它看懂就OK了,其实这个跟普通的线段树也没太大区别,就是存的是出现的次数. CODE #include<algorithm> #include<cstdio> ...

  2. [权值线段树] Jzoj P4270 魔道研究

    Description "我希望能使用更多的魔法.不对,是预定能使用啦.最终我要被大家称呼为大魔法使.为此我决定不惜一切努力." --<The Grimoire of Mar ...

  3. 【BZOJ4605】崂山白花蛇草水 权值线段树+kd-tree

    [BZOJ4605]崂山白花蛇草水 Description 神犇Aleph在SDOI Round2前立了一个flag:如果进了省队,就现场直播喝崂山白花蛇草水.凭借着神犇Aleph的实力,他轻松地进了 ...

  4. 【bzoj4605】崂山白花蛇草水 权值线段树套KD-tree

    题目描述 神犇Aleph在SDOI Round2前立了一个flag:如果进了省队,就现场直播喝崂山白花蛇草水.凭借着神犇Aleph的实力,他轻松地进了山东省省队,现在便是他履行诺言的时候了.蒟蒻Bob ...

  5. 崂山白花蛇草水 权值线段树套KDtree

    崂山白花蛇草水 权值线段树套KDtree Description 神犇Aleph在SDOI Round2前立了一个flag:如果进了省队,就现场直播喝崂山白花蛇草水.凭借着神犇Aleph的实 力,他轻 ...

  6. BZOJ 4605 崂山白花蛇草水 权值线段树+K-D树

    Description 神犇Aleph在SDOI Round2前立了一个flag:如果进了省队,就现场直播喝崂山白花蛇草水.凭借着神犇Aleph的实 力,他轻松地进了山东省省队,现在便是他履行诺言的时 ...

  7. BZOJ 4605: 崂山白花蛇草水 树套树 权值线段树套kdtree

    4605: 崂山白花蛇草水 Time Limit: 80 Sec  Memory Limit: 512 MB Submit: 527  Solved: 153 [Submit][Status][Dis ...

  8. 【bzoj2770】YY的Treap 权值线段树

    题目描述 志向远大的YY小朋友在学完快速排序之后决定学习平衡树,左思右想再加上SY的教唆,YY决定学习Treap.友爱教教父SY如砍瓜切菜般教会了YY小朋友Treap(一种平衡树,通过对每个节点随机分 ...

  9. codevs1688 求逆序对(权值线段树)

    1688 求逆序对  时间限制: 1 s  空间限制: 128000 KB  题目等级 : 黄金 Gold 题解  查看运行结果 题目描述 Description 给定一个序列a1,a2,-,an,如 ...

最新文章

  1. 【Spark】Spark SQL, DataFrames and Datasets Guide(翻译文,持续更新)
  2. 关于《计算机程序的构造和解释》
  3. NSArray 与 NSMutableArray 的排序
  4. sizeof()与strlen()
  5. ==与equals 的使用比较
  6. zabbix proxy mysql_zabbix proxy 配置
  7. Multi GET API介绍
  8. Python 学习笔记——文件对象和操作
  9. NYOJ--4--ASCII码排序
  10. 每天一点正则表达式积累之(?=X)和(?!X)测试(七)
  11. Android的Splash界面支持用户点击
  12. Atitit 表达式原理 语法分析 原理与实践 解析java的dsl  递归下降是现阶段主流的语法分析方法
  13. hustoj搭建教程
  14. 华为“天才少年”稚晖君又出新作,从零开始造“客制化”智能键盘
  15. oracle创建一个永久性表空间,Oracle表空间简单管理永久表空间
  16. 戴尔服务器bios设置u盘启动不了系统,戴尔电脑主板bios设置u盘启动不了怎么办...
  17. pr中创建镜像效果,并用渐变进行过渡
  18. [SHELL]: ln 命令详解
  19. Deep Learning(深度学习)学习笔记整理
  20. 绘图和可视化(Python)

热门文章

  1. 云原生周报:第 3 期
  2. hammer实现拖拽旋转缩放功能
  3. 英伟达冠军!FB-OCC:CVPR23 3D占用预测冠军方案解读
  4. mustache模板技术简介
  5. 整个公司的电脑时间比北京时间快三分钟
  6. 可视化绘图技巧100篇基础篇(一)-棒棒图
  7. NOI-OJ 3.9 ID:3344 冷血格斗场
  8. java axis 拒绝连接_java – org.apache.axis2.AxisFault连接被拒绝
  9. oracle flashback 用法,使用Oracle10g Flashback database功能恢复用户错误
  10. CB官方推荐AP英语文学与写作必读书目,2023报名中