[BZOJ]3110 K大数查询(ZJOI2013)
这大概是唯一一道小C重写了4次的题目。
姿势不对的树套树(Fail) → 分块(Fail) → 整体二分(Succeed) → 树套树(Succeed)。
让小C写点心得静静。
Description
有N个位置,M个操作。操作有两种,每次操作如果是1 a b c的形式表示在第a个位置到第b个位置,每个位置加入一个数c。如果是2 a b c形式,表示询问从第a个位置到第b个位置,第C大的数是多少。
Input
第一行N,M。
接下来M行,每行形如1 a b c或2 a b c。
Output
输出每个询问的结果
Sample Input
2 5
1 1 2 1
1 1 2 2
2 1 1 2
2 1 1 1
2 1 2 3
Sample Output
1
2
1
HINT
N,M<=50000,N,M<=50000
a<=b<=N
1操作中abs(c)<=N
2操作中c<=Maxlongint
Solution
这道题让小C见识了整体二分的妙用,它是一种离线做法。
整体二分的思想就是二分答案。以单次询问的区间K大为例,我们可以二分答案,通过统计比当前二分出的答案大的数的个数,与K比较,决定缩小或是增大答案。
而对于多次询问,我们整体二分所有询问的答案,对于每个询问,我们都通过比较,来决定它们应该进入小的那一半,还是大的那一半。
而对于每个修改,我们也要判断它会对哪个区间的答案产生影响,来决定它应该进入哪一半。
要注意,任何时候,每个询问和修改之间的相对顺序都不能变。
这样的时间复杂度为什么是科学的呢?我们脑补一下:

动态询问的本质就是边修改边询问的过程,对于每次修改,我们用数据结构维护要统计的信息。(这是一句废话)
因为有数据结构的存在,时间复杂度才是科学的。(还不理解的读者请将这一行无视,继续往下看)
What?还需要数据结构?那我要整体二分何用?我咋不大力树套树呢?
整体二分当然不是白写的。原来树套树要维护的是,值域区间[L,R]内,数组的信息,需要二维。
而整体二分时,我们只需要维护 当前 二分的值域区间[L,R]内,数组的信息,只需要一维。
因为做完 当前 这段值域区间内的事情后,这棵线段树/平衡树就彻底没用了,因为关于它的所有事情都做完了,清空就行。(理解了再往下看吧)
这就巧妙利用离线做法将数据结构降维,空间和时间(常数)上都有很大的提升。
最后证明一下时间复杂度:对于每个询问和修改,我们都在二分答案的 log 个区间内做了一遍,总时间复杂度。
当然具体实现还有一些细节,还不理解的可以参见小C的代码。
说说这道题的题解:
树套树:以值域为第一维,以数组下标为第二维,第二维维护区间和。对于每个询问,像二分答案一样在第一维上走到底。
整体二分:离线,把树套树做法降维。
整体二分:
#include <cstdio>
#include <cstring>
#include <algorithm>
#define l(a) son[a][0]
#define r(a) son[a][1]
#define MN 50005
#define ll long long
using namespace std;
struct meg{int g,x,y,z,pos,gs;}q[MN],q1[MN],q2[MN];
int son[MN<<][],tg[MN<<],din,rt;
int ans[MN],n,m,ain;
ll t[MN<<]; inline int read()
{
int n=,f=; char c=getchar();
while (c<'' || c>'') {if(c=='-')f=-; c=getchar();}
while (c>='' && c<='') {n=n*+c-''; c=getchar();}
return n*f;
} inline void mark(int &x,int L,int R,int z)
{
if (!x) {x=++din; l(x)=r(x)=t[x]=tg[x]=;}
t[x]+=1LL*z*(R-L+); tg[x]+=z;
}
inline void down(int x,int L,int R)
{
if (!tg[x]) return;
int mid=L+R>>;
mark(l(x),L,mid,tg[x]); mark(r(x),mid+,R,tg[x]);
tg[x]=;
} ll getsum(int x,int L,int R,int ql,int qr)
{
if (!x) return ;
if (ql==L&&qr==R) return t[x];
down(x,L,R);
int mid=L+R>>;
if (qr<=mid) return getsum(l(x),L,mid,ql,qr);
else if (ql>mid) return getsum(r(x),mid+,R,ql,qr);
else return getsum(l(x),L,mid,ql,mid)+getsum(r(x),mid+,R,mid+,qr);
}
void add(int &x,int L,int R,int ql,int qr)
{
if (!x) {x=++din; l(x)=r(x)=t[x]=tg[x]=;}
if (ql==L&&qr==R) {mark(x,ql,qr,); return;}
down(x,L,R);
int mid=L+R>>;
if (qr<=mid) add(l(x),L,mid,ql,qr);
else if (ql>mid) add(r(x),mid+,R,ql,qr);
else add(l(x),L,mid,ql,mid),add(r(x),mid+,R,mid+,qr);
t[x]=t[l(x)]+t[r(x)];
} void work(int L,int R,int hd,int tl)
{
if (hd>tl) return;
register int i;
if (L==R) {for (i=hd;i<=tl;++i) if (q[i].g==) ans[q[i].pos]=L; return;}
int mid=L+R>>,p1=,p2=;
ll lt;
rt=din=;
for (i=hd;i<=tl;++i)
{
if (q[i].g==)
{
if (q[i].z>mid) add(rt,,n,q[i].x,q[i].y),q2[++p2]=q[i];
else q1[++p1]=q[i];
}
else if (q[i].g==)
{
lt=getsum(rt,,n,q[i].x,q[i].y);
if (lt+q[i].gs>=q[i].z) q2[++p2]=q[i];
else q[i].gs+=lt,q1[++p1]=q[i];
}
}
for (i=;i<=p1;++i) q[hd+i-]=q1[i];
for (i=;i<=p2;++i) q[hd+p1+i-]=q2[i];
work(L,mid,hd,hd+p1-); work(mid+,R,hd+p1,tl);
} int main()
{
register int i;
n=read(); m=read();
for (i=;i<=m;++i) {q[i].g=read(); q[i].x=read(); q[i].y=read(); q[i].z=read(); if (q[i].g==) q[i].pos=++ain;}
work(-n,n,,m);
for (i=;i<=ain;++i) printf("%d\n",ans[i]);
}
树套树:
#include <cstdio>
#include <cstring>
#include <algorithm>
#define l(a) (a<<1)
#define r(a) (a<<1|1)
#define L(a) (son[a][0])
#define R(a) (son[a][1])
#define TMN 15000005
#define MN 50005
#define ll long long
using namespace std;
ll t[TMN];
int son[TMN][],tg[TMN],rt[MN<<],din;
int n,m; inline int read()
{
int n=,f=; char c=getchar();
while (c<'' || c>'') {if(c=='-')f=-; c=getchar();}
while (c>='' && c<='') {n=n*+c-''; c=getchar();}
return n*f;
} ll Tgetsum(int x,int L,int R,int ql,int qr)
{
if (!x) return ;
if (ql==L&&qr==R) return t[x];
int mid=L+R>>;
ll lt=1LL*tg[x]*(qr-ql+);
if (qr<=mid) return lt+Tgetsum(L(x),L,mid,ql,qr);
else if (ql>mid) return lt+Tgetsum(R(x),mid+,R,ql,qr);
else return lt+Tgetsum(L(x),L,mid,ql,mid)+Tgetsum(R(x),mid+,R,mid+,qr);
}
void Tadd(int &x,int L,int R,int ql,int qr)
{
if (!x) x=++din;
if (ql==L&&qr==R) {++tg[x]; t[x]+=(ll)R-L+; return;}
int mid=L+R>>;
if (qr<=mid) Tadd(L(x),L,mid,ql,qr);
else if (ql>mid) Tadd(R(x),mid+,R,ql,qr);
else Tadd(L(x),L,mid,ql,mid),Tadd(R(x),mid+,R,mid+,qr);
t[x]=t[L(x)]+t[R(x)]+1LL*tg[x]*(R-L+);
} int getk(int x,int L,int R,int z,int yl,int yr)
{
if (L==R) return L;
ll lt;
int mid=L+R>>;
if ((lt=Tgetsum(rt[r(x)],,n,yl,yr))<z) return getk(l(x),L,mid,z-lt,yl,yr);
else return getk(r(x),mid+,R,z,yl,yr);
}
void add(int x,int L,int R,int q,int yl,int yr)
{
Tadd(rt[x],,n,yl,yr);
if (L==R) return;
int mid=L+R>>;
if (q<=mid) add(l(x),L,mid,q,yl,yr);
else add(r(x),mid+,R,q,yl,yr);
} int main()
{
register int g,x,y,z;
n=read(); m=read();
while (m--)
{
g=read(); x=read(); y=read(); z=read();
if (g==) add(,-n,n,z,x,y);
else if (g==) printf("%d\n",getk(,-n,n,z,x,y));
}
}
Last Word
说说前两次打这题的惨痛经历吧:
第一次树套树把(相对于正解)第一维和第二维搞反了,导致空间爆炸。
第二次分块,脑补了一个时间和空间都是O(n*sqrt(n)*log(n))的做法(权值线段树+替罪羊树瞎写),空间还是爆炸。
两次做法的思路都是二分答案,而且都把数组下标看作是第一维。
不过所幸打的这几次都没怎么需要Debug。打得挺爽。
[BZOJ]3110 K大数查询(ZJOI2013)的更多相关文章
- BZOJ 3110 K大数查询 | 整体二分
BZOJ 3110 K大数查询 题面 有N个位置,M个操作.操作有两种,每次操作如果是1 a b c的形式表示在第a个位置到第b个位置,每个位置加入一个数c 如果是2 a b c形式,表示询问从第a个 ...
- BZOJ 3110 k大数查询 & 树套树
题意: 有n个位置,每个位置可以看做一个集合,现在要求你实现一个数据结构支持以下功能: 1:在a-b的集合中插入一个数 2:询问a-b集合中所有元素的第k大. SOL: 调得火大! 李建说数据结构题能 ...
- bzoj 3110 K大数查询
第一道整体二分,因为只需要知道每个询问区间中比mid大的数有多少个,就可以直接用线段树区间加,区间求和了. #include<iostream> #include<cstdio> ...
- BZOJ 3110: [Zjoi2013]K大数查询 [树套树]
3110: [Zjoi2013]K大数查询 Time Limit: 20 Sec Memory Limit: 512 MBSubmit: 6050 Solved: 2007[Submit][Sta ...
- 树套树专题——bzoj 3110: [Zjoi2013] K大数查询 & 3236 [Ahoi2013] 作业 题解
[原题1] 3110: [Zjoi2013]K大数查询 Time Limit: 20 Sec Memory Limit: 512 MB Submit: 978 Solved: 476 Descri ...
- bzoj 3110: [Zjoi2013]K大数查询 树状数组套线段树
3110: [Zjoi2013]K大数查询 Time Limit: 20 Sec Memory Limit: 512 MBSubmit: 1384 Solved: 629[Submit][Stat ...
- BZOJ 3110: [Zjoi2013]K大数查询( 树状数组套主席树 )
BIT+(可持久化)权值线段树, 用到了BIT的差分技巧. 时间复杂度O(Nlog^2(N)) ---------------------------------------------------- ...
- BZOJ 3110([Zjoi2013]K大数查询-区间第k大[段修改,在线]-树状数组套函数式线段树)
3110: [Zjoi2013]K大数查询 Time Limit: 20 Sec Memory Limit: 512 MB Submit: 418 Solved: 235 [ Submit][ ...
- BZOJ 3110 [Zjoi2013]K大数查询(整体二分)
3110: [Zjoi2013]K大数查询 Time Limit: 20 Sec Memory Limit: 512 MBSubmit: 11654 Solved: 3505[Submit][St ...
随机推荐
- 日志 --BUG记录
2014-12-15日 在做520wawa的免费推广 部署web应用时 错把path设置为"/*",导致启动tomcat时,导致错误 <Context path=&quo ...
- 清华集训2015 V
#164. [清华集训2015]V http://uoj.ac/problem/164 统计 描述 提交 自定义测试 Picks博士观察完金星凌日后,设计了一个复杂的电阻器.为了简化题目,题目中的常数 ...
- Vim 中文社区:期待你的加入
我们的愿景 Vim 中文社区一直比较零散,缺少凝聚力,现有的一些群经常也是水的可以的,讨论各种无关紧要的内容,于是导致很大一部分人,将这些群丢入了群助手,渐渐地他们也淡出了 vim 中文社区. 而我理 ...
- JAVA_SE基础——31.this关键字
黑马程序员入学blog... 也算是学习笔记体会. this的通俗解释: 有一个A类,一个B方法,一个C变量,其中B和C都在类A中 this.B()就是调用A类中的B方法 this.C=1(假设C是一 ...
- pythoncharm 中解决启动server时出现 “django.core.exceptions.ImproperlyConfigured: Requested setting DEBUG, but settings are not configured”的错误
背景介绍 最近,尝试着用pythoncharm 这个All-star IDE来搞一搞Django,于是乎,下载专业版,PJ等等一系列操作之后,终于得偿所愿.可以开工了. 错误 在园子里找了一篇初学者的 ...
- api-gateway实践(16)【租户模块:修改api定义】通过mq通知【开发者模块:更新开发者集市】
一.订阅关系 二.接收消息 dev模块接收更新本地集市
- Docker的容器操作
启动一次性运行的容器 入门级例子:从ubuntu:14.04镜像启动一个容器,成功后在容器内部执行/bin/echo 'hello world'命令,如果当前物理机没有该镜像,则执行docker pu ...
- maven的使用之一简单的安装
首先,我们知道,在传统的项目中,我们会导入一堆的jar包,那样的话,我们会发现我们的jar包的大小已经占了整个项目大小的90%以上,甚至更多,而且,我们的jar包只能自己使用,如果 其他人想用的话,还 ...
- linux下查看mysql日志文件的方法
查看mysql日志方法: mysql默认不允许我们查看日志.需要更改一些设置 1 vi 更改配置文件 允许用户查看日志文件 sudo vi /etc/mysql/mysql.conf.d/mysqld ...
- python开发:python基本数据类型
运算符 1.算数运算: 2.比较运算: 3.赋值运算: 4.逻辑运算: 5.成员运算: 基本数据类型 1.数字 int(整型) 在32位机器上,整数的位数为32位,取值范围为-2**31-2**31- ...