[BZOJ 3110] [luogu 3332] [ZJOI 2013]k大数查询(权值线段树套线段树)
[BZOJ 3110] [luogu 3332] [ZJOI 2013]k大数查询(权值线段树套线段树)
题面
原题面有点歧义,不过从样例可以看出来真正的意思
有n个位置,每个位置可以看做一个集合。
1 a b c :在a-b的每个集合中插入一个数c
2 a b c :2:询问将a-b的每个集合合并到一起后所有元素的第c大
分析
外层用权值线段树维护值,内层用普通线段树维护位置
我们先考虑全局询问第k大的情况,显然只需要权值线段树维护全局值的出现情况,区间[L,R]存储值落在[L,R]内的元素数量,然后只要在线段树上二分即可
但是我们要询问的值有位置范围,这时权值线段树的节点就不能仅仅存储一个值了,而是存储一个线段树(动态开点)。外层线段树的每个节点[L,R],对应一棵内层维护位置的线段树,内层线段树的节点[l,r]维护有多少个值为[L,R]的节点的位置在[l,r]内
这样区间询问第k大的时候只需要在内层线段树上区间查询即可。
修改的时候我们先在外层线段树上找到包含c的区间,然后内层线段树上的位置i(a<=i<=b)需要+1,因为多了位置在i上的值。直接对每棵内层线段树做区间加法即可
时间复杂度\(O(m\log ^2n)\)
代码
//https://www.luogu.org/problem/P3332
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxn 50000
#define maxm 50000
#define maxlogn 60
using namespace std;
typedef long long ll;
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;
struct segment_tree_pos{
//内层维护位置的线段树,维护外层权值线段树区间[L,R]内的数的出现情况
//叶子节点存储第i个位置的集合里有多少个在[L,R]内的数,区间求和
#define lson(x) tree[x].ls
#define rson(x) tree[x].rs
struct node{
int ls;
int rs;
ll v;
ll mark;
}tree[maxn*4*maxlogn+5];
int ptr=0;
void push_up(int x){
tree[x].v=tree[lson(x)].v+tree[rson(x)].v;
}
void push_down(int x,int l,int r){
int mid=(l+r)>>1;
if(tree[x].mark){
if(!lson(x)) tree[x].ls=++ptr;
if(!rson(x)) tree[x].rs=++ptr;
tree[lson(x)].mark+=tree[x].mark;
tree[lson(x)].v+=tree[x].mark*(mid-l+1);
tree[rson(x)].mark+=tree[x].mark;
tree[rson(x)].v+=tree[x].mark*(r-mid);
tree[x].mark=0;
}
}
void update(int &x,int val,int L,int R,int l,int r){
if(!x) x=++ptr;
if(L<=l&&R>=r){
tree[x].mark+=val;
tree[x].v+=val*(r-l+1);
return;
}
push_down(x,l,r);
int mid=(l+r)>>1;
if(L<=mid) update(tree[x].ls,val,L,R,l,mid);
if(R>mid) update(tree[x].rs,val,L,R,mid+1,r);
push_up(x);
}
ll query(int x,int L,int R,int l,int r){
if(!x) return 0;
if(L<=l&&R>=r){
return tree[x].v;
}
push_down(x,l,r);
int mid=(l+r)>>1;
ll ans=0;
if(L<=mid) ans+=query(tree[x].ls,L,R,l,mid);
if(R>mid) ans+=query(tree[x].rs,L,R,mid+1,r);
return ans;
}
#undef lson
#undef rson
}T1;
struct segment_tree_val{//外层权值线段树
struct node{
int l;
int r;
int root;
}tree[maxn*4+5];
void build(int x,int l,int r){
tree[x].l=l;
tree[x].r=r;
if(l==r) return;
int mid=(l+r)>>1;
build(x<<1,l,mid);
build(x<<1|1,mid+1,r);
}
void update(int x,int val,int L,int R){ //找到包含val的权值区间,在权值区间对应的内层线段树上区间修改
T1.update(tree[x].root,1,L,R,1,n);
if(tree[x].l==tree[x].r) return;
int mid=(tree[x].l+tree[x].r)>>1;
if(val<=mid) update(x<<1,val,L,R);
else update(x<<1|1,val,L,R);
}
int query(int x,int k,int L,int R){
if(tree[x].l==tree[x].r) return tree[x].l;
//注意是查询第k大
ll cnt=T1.query(tree[x<<1|1].root,L,R,1,n);//查询右子结点权值区间[l,mid]中,位置落在[L,R]内的有多少个
if(k<=cnt) return query(x<<1|1,k,L,R);
else return query(x<<1,k-cnt,L,R);
}
}T2;
struct oper{
int type;
int a;
int b;
int c;
}q[maxm+5];
int dn;
int tmp[maxm+5];
int main(){
qread(n);
qread(m);
for(int i=1;i<=m;i++){
qread(q[i].type);
qread(q[i].a);
qread(q[i].b);
qread(q[i].c);
if(q[i].type==1) tmp[++dn]=q[i].c;
}
sort(tmp+1,tmp+1+dn);
dn=unique(tmp+1,tmp+1+dn)-tmp-1;
for(int i=1;i<=m;i++){
if(q[i].type==1) q[i].c=lower_bound(tmp+1,tmp+1+dn,q[i].c)-tmp;
}
T2.build(1,1,dn);
for(int i=1;i<=m;i++){
if(q[i].type==1){
T2.update(1,q[i].c,q[i].a,q[i].b);
}else{
qprint(tmp[T2.query(1,q[i].c,q[i].a,q[i].b)]);//由于离散化过,tmp[x]才是真实的值
putchar('\n');
}
}
}
[BZOJ 3110] [luogu 3332] [ZJOI 2013]k大数查询(权值线段树套线段树)的更多相关文章
- BZOJ 3110 ZJOI 2013 K大数查询 树套树(权值线段树套区间线段树)
题目大意:有一些位置.这些位置上能够放若干个数字. 如今有两种操作. 1.在区间l到r上加入一个数字x 2.求出l到r上的第k大的数字是什么 思路:这样的题一看就是树套树,关键是怎么套,怎么写.(话说 ...
- 数据结构(树套树):ZJOI 2013 K大数查询
有几个点卡常数…… 发现若第一维为位置,第二维为大小,那么修改时第一维修改区间,查询时第一维查询区间,必须挂标记.而这种情况下标记很抽象,而且Push_down不是O(1)的,并不可行. 那要怎么做呢 ...
- 【bzoj3110】[Zjoi2013]K大数查询 权值线段树套区间线段树
题目描述 有N个位置,M个操作.操作有两种,每次操作如果是1 a b c的形式表示在第a个位置到第b个位置,每个位置加入一个数c.如果是2 a b c形式,表示询问从第a个位置到第b个位置,第C大的数 ...
- BZOJ3110[Zjoi2013]K大数查询——权值线段树套线段树
题目描述 有N个位置,M个操作.操作有两种,每次操作如果是1 a b c的形式表示在第a个位置到第b个位置,每个位置加入一个数c如果是2 a b c形式,表示询问从第a个位置到第b个位置,第C大的数是 ...
- [BZOJ 3110] [ZJOI 2013] K大数查询
Description 有 \(N\) 个位置,\(M\) 个操作.操作有两种,每次操作如果是: 1 a b c:表示在第 \(a\) 个位置到第 \(b\) 个位置,每个位置加入一个数 \(c\): ...
- [ZJOI 2013] K大数查询
[题目链接] https://www.lydsy.com/JudgeOnline/problem.php?id=3110 [算法] 整体二分 + 线段树 时间复杂度 : O(NlogN ^ 2) [代 ...
- 解题:ZJOI 2013 K大数查询
题面 树套树,权值线段树套序列线段树,每次在在权值线段树上的每棵子树上做区间加,查询的时候左右子树二分 本来想两个都动态开点的,这样能体现树套树在线的优越性.但是常数太大惹,所以外层直接固定建树了QA ...
- 洛谷P3332 [ZJOI2013]K大数查询 权值线段树套区间线段树_标记永久化
Code: #include <cstdio> #include <algorithm> #include <string> #include <cstrin ...
- P3332 [ZJOI2013]K大数查询(线段树套线段树+标记永久化)
P3332 [ZJOI2013]K大数查询 权值线段树套区间线段树 把插入的值离散化一下开个线段树 蓝后每个节点开个线段树,维护一下每个数出现的区间和次数 为了防止MLE动态开点就好辣 重点是标记永久 ...
随机推荐
- MyEclipse使用教程:使用Workbench和Perspectives
[MyEclipse CI 2019.4.0安装包下载] workbench指的是加载IDE时看到的内容,它通常包含一个perspective,这是相关视图和编辑器的布局.您可以根据正在进行开发的类型 ...
- MongoDB之自动启动服务
安装详细步骤请点我 为了能让NoSQLBooster for MongoDB连接的时候不报错,将mongodb添加到系统服务中. 在C:\Program Files\MongoDB\Server\3. ...
- 用设计模式来替代if-else
前言 物流行业中,通常会涉及到EDI报文(XML格式文件)传输和回执接收,每发送一份EDI报文,后续都会收到与之关联的回执(标识该数据在第三方系统中的流转状态).这里枚举几种回执类型:MT1101.M ...
- MySQL把多条数据给汇总成一条数据
用到的是这个函数: group_concat() select group_buying_id, group_concat(app_user_ids) from org_user_group grou ...
- jAVA基础 提高文件复制性能之多线程复制文件
利用IO流中的随机访问文件 RandomAccessFile 和文件通道 FileChanne 复制文件可大大提高文件的读写效率,在此基础上利用多线程复制文件使其性能更优.因线程的个数可根据文件的大小 ...
- Java——API文档
Sun下载JDK--解压缩--javadoc文件(Constuctor Summary[构造方法]+Method Summary[方法]) [Object] Object类是所有Java类的根 ...
- docker-compose简介
一.Docker-Compose简介 Docker-Compose项目是Docker官方的开源项目,负责实现对Docker容器集群的快速编排. Docker-Compose将所管理的容器分为三 ...
- 【PowerOJ1754&网络流24题】负载平衡问题(费用流)
题意: 思路: [问题分析] 转化为供求平衡问题,用最小费用最大流解决. [建模方法] 首先求出所有仓库存货量平均值,设第i个仓库的盈余量为A[i],A[i] = 第i个仓库原有存货量 - 平均存货量 ...
- PHP CGI
cgi是通用网关接口,是连接web服务器和应用程序的接口. web服务器负责接收http请求,但是http请求从request到response的过程需要有应用程序的逻辑处理,web服务器一般是使用C ...
- vue 实现,子组件向父组件 传递数据
首先理清组件之间的关系 组件与组件之间,还存在着不同的关系.父子关系与兄弟关系(不是父子的都暂称为兄弟吧). 父子组件 父子关系即是组件 A 在它的模板中使用了组件 B,那么组件 A 就是父组件,组件 ...