【题解】有便便的厕所(权值线段树动态开点模板题)
我只是来填坑的。
关于权值线段树的更多有关内容见此。
题面
题目描述
众所周知, 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.
【题解】有便便的厕所(权值线段树动态开点模板题)相关推荐
- 权值线段树+动态开点(学习小结)
首先先上一道板题:把它看懂就OK了,其实这个跟普通的线段树也没太大区别,就是存的是出现的次数. CODE #include<algorithm> #include<cstdio> ...
- [权值线段树] Jzoj P4270 魔道研究
Description "我希望能使用更多的魔法.不对,是预定能使用啦.最终我要被大家称呼为大魔法使.为此我决定不惜一切努力." --<The Grimoire of Mar ...
- 【BZOJ4605】崂山白花蛇草水 权值线段树+kd-tree
[BZOJ4605]崂山白花蛇草水 Description 神犇Aleph在SDOI Round2前立了一个flag:如果进了省队,就现场直播喝崂山白花蛇草水.凭借着神犇Aleph的实力,他轻松地进了 ...
- 【bzoj4605】崂山白花蛇草水 权值线段树套KD-tree
题目描述 神犇Aleph在SDOI Round2前立了一个flag:如果进了省队,就现场直播喝崂山白花蛇草水.凭借着神犇Aleph的实力,他轻松地进了山东省省队,现在便是他履行诺言的时候了.蒟蒻Bob ...
- 崂山白花蛇草水 权值线段树套KDtree
崂山白花蛇草水 权值线段树套KDtree Description 神犇Aleph在SDOI Round2前立了一个flag:如果进了省队,就现场直播喝崂山白花蛇草水.凭借着神犇Aleph的实 力,他轻 ...
- BZOJ 4605 崂山白花蛇草水 权值线段树+K-D树
Description 神犇Aleph在SDOI Round2前立了一个flag:如果进了省队,就现场直播喝崂山白花蛇草水.凭借着神犇Aleph的实 力,他轻松地进了山东省省队,现在便是他履行诺言的时 ...
- BZOJ 4605: 崂山白花蛇草水 树套树 权值线段树套kdtree
4605: 崂山白花蛇草水 Time Limit: 80 Sec Memory Limit: 512 MB Submit: 527 Solved: 153 [Submit][Status][Dis ...
- 【bzoj2770】YY的Treap 权值线段树
题目描述 志向远大的YY小朋友在学完快速排序之后决定学习平衡树,左思右想再加上SY的教唆,YY决定学习Treap.友爱教教父SY如砍瓜切菜般教会了YY小朋友Treap(一种平衡树,通过对每个节点随机分 ...
- codevs1688 求逆序对(权值线段树)
1688 求逆序对 时间限制: 1 s 空间限制: 128000 KB 题目等级 : 黄金 Gold 题解 查看运行结果 题目描述 Description 给定一个序列a1,a2,-,an,如 ...
最新文章
- 【Spark】Spark SQL, DataFrames and Datasets Guide(翻译文,持续更新)
- 关于《计算机程序的构造和解释》
- NSArray 与 NSMutableArray 的排序
- sizeof()与strlen()
- ==与equals 的使用比较
- zabbix proxy mysql_zabbix proxy 配置
- Multi GET API介绍
- Python 学习笔记——文件对象和操作
- NYOJ--4--ASCII码排序
- 每天一点正则表达式积累之(?=X)和(?!X)测试(七)
- Android的Splash界面支持用户点击
- Atitit 表达式原理 语法分析 原理与实践 解析java的dsl 递归下降是现阶段主流的语法分析方法
- hustoj搭建教程
- 华为“天才少年”稚晖君又出新作,从零开始造“客制化”智能键盘
- oracle创建一个永久性表空间,Oracle表空间简单管理永久表空间
- 戴尔服务器bios设置u盘启动不了系统,戴尔电脑主板bios设置u盘启动不了怎么办...
- pr中创建镜像效果,并用渐变进行过渡
- [SHELL]: ln 命令详解
- Deep Learning(深度学习)学习笔记整理
- 绘图和可视化(Python)
热门文章
- 云原生周报:第 3 期
- hammer实现拖拽旋转缩放功能
- 英伟达冠军!FB-OCC:CVPR23 3D占用预测冠军方案解读
- mustache模板技术简介
- 整个公司的电脑时间比北京时间快三分钟
- 可视化绘图技巧100篇基础篇(一)-棒棒图
- NOI-OJ 3.9 ID:3344 冷血格斗场
- java axis 拒绝连接_java – org.apache.axis2.AxisFault连接被拒绝
- oracle flashback 用法,使用Oracle10g Flashback database功能恢复用户错误
- CB官方推荐AP英语文学与写作必读书目,2023报名中