整体二分初探 两类区间第K大问题 poj2104 & hdu5412
看到好多讲解都把整体二分和$CDQ$分治放到一起讲 不过自己目前还没学会$CDQ$分治 就单独谈谈整体二分好了
先推荐一下$XHR$的 <浅谈数据结构题的几个非经典解法> 整体二分在当中有较为详细的讲解
先来说一下静态第$K$小的整体二分解法 $(POJ2104)$
题目链接:http://poj.org/problem?id=2104
所谓整体二分 就是利用所有的询问相互独立而把它们$($此题没有修改操作$)$通过二分把它们分治进行处理
不妨先来考虑下一个简单易懂的$O(NlogS)$的排序算法$(S$为数值范围$)$
这个方法是自己在思考整体二分的时候$yy$的 虽然在实际应用上没什么意义 但是有助于理解整体二分的分治过程
我们假设当前处理的区间里最小值不小于$L$ 最大值不大于$R$ 令$MID = (L + R) / 2$
然后把当前区间扫描一遍 如果一个数不大于$MID$就放到左子区间 否则放到右子区间
如此下去 直到区间内只剩一个数或者$L$ 与 $R$相等 排序就完成了
现在回到静态区间第$K$小问题 和刚才那个排序算法类似 我们先二分一个答案$MID$
如果区间内小于等于$MID$的数的个数$($记为$CNT)$不超过$K$ 那么最终答案显然也是不超过$MID$的
这类询问我们把它们划分到左子区间
而对于$CNT$大于$K$的 我们则把它们划分到右子区间 并且把$K$减去$CNT$
换句话说就是把小于等于$MID$的数的贡献全部算上后之后就不用考虑了
可以发现这样划分的层数是$logS$ 而每一层的询问个数是$Q$个 再加上算贡献时用到的$BIT$ 所以复杂度是$O(QlogNlogS)$
以下是$poj2104$参考代码
- //#include <bits/stdc++.h>
- #include <cstdio>
- #include <cstring>
- #include <cmath>
- #include <algorithm>
- using namespace std;
- const int N = 1e5 + , Q = 5e3 + , lim = 1e9;
- struct point
- {
- int x, num;
- }a[N];
- struct query
- {
- int x, y, k, cnt, num;
- }q[Q], b[Q];
- int sum[N], ans[Q];
- int n, m;
- bool cmp(const point &aa, const point &bb)
- {
- return aa.x < bb.x;
- }
- void calc(int ll, int rr, int rawL, int mid)
- {
- int L = , R = n + , MID;
- while(L < R)
- {
- MID = (L + R) >> ;
- if(a[MID].x >= rawL)
- R = MID;
- else
- L = MID + ;
- }
- for(int i = R; i <= n && a[i].x <= mid; ++i)
- for(int j = a[i].num; j <= n; j += (j & -j))
- ++sum[j];
- for(int i = ll; i <= rr; ++i)
- {
- q[i].cnt = ;
- for(int j = q[i].y; j; j -= (j & -j))
- q[i].cnt += sum[j];
- for(int j = q[i].x - ; j; j -= (j & -j))
- q[i].cnt -= sum[j];
- }
- for(int i = R; i <= n && a[i].x <= mid; ++i)
- for(int j = a[i].num; j <= n; j += (j & -j))
- --sum[j];
- }
- void divide(int ll, int rr, int rawL, int rawR)
- {
- if(rawL == rawR)
- {
- for(int i = ll; i <= rr; ++i)
- ans[q[i].num] = rawR;
- return;
- }
- int mid = rawL + ((rawR - rawL) >> );
- calc(ll, rr, rawL, mid);
- int now1 = ll, now2 = rr;
- for(int i = ll; i <= rr; ++i)
- {
- if(q[i].cnt >= q[i].k)
- b[now1++] = q[i];
- else
- {
- q[i].k -= q[i].cnt;
- b[now2--] = q[i];
- }
- }
- for(int i = ll; i <= rr; ++i)
- q[i] = b[i];
- if(now1 != ll)
- divide(ll, now1 - , rawL, mid);
- if(now2 != rr)
- divide(now2 + , rr, mid + , rawR);
- }
- int main()
- {
- scanf("%d%d", &n, &m);
- for(int i = ; i <= n; ++i)
- {
- scanf("%d", &a[i].x);
- a[i].num = i;
- }
- a[n + ].x = 2e9;
- sort(a + , a + + n, cmp);
- for(int i = ; i <= m; ++i)
- {
- scanf("%d%d%d", &q[i].x, &q[i].y, &q[i].k);
- q[i].num = i;
- }
- divide(, m, -lim, lim);
- for(int i = ; i <= m; ++i)
- printf("%d\n", ans[i]);
- return ;
- }
然后就是整体二分真正突出存在意义的问题 动态区间第$K$大了$(hdu5412)$
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5412
动态区间第$K$大就是额外多了修改操作 修改不仅对询问有影响 对修改也有影响 因此直观上看起来要麻烦很多
我们知道 区间询问之所以能排序后分治 主要是所有的询问相互独立
对于修改操作 如果我们希望它们相互独立该怎么做呢 那就算贡献好了
实际上 每次把一个位置的数修改为另一个数相当于使这个位置删除一个原来的数 并插入一个新的数 而初始的数则相当于仅有插入操作
这样只要保证同一区间内的查询和删除或插入操作的相对顺序不变 即相对时间顺序不变 最后算贡献结果也是一样的
并且由于对大于$MID$的数的删除或插入操作 即便时间顺序是在划分到左子区间的询问之前 也不会造成影响 因此它们可以划分到右子区间
而对小于$MID$的数的删除或插入操作 对划分到右子区间的询问的影响可以直接在划分前计入贡献之后不再考虑 因此它们可以划分到左子区间
这样按照类似静态区间第$K$大问题计算复杂度会发现是$O((Q+N)logNlogS)$
以下是$hdu5412$的参考代码
- #include <bits/stdc++.h>
- using namespace std;
- const int N = 1e5 + , Q = 1e5 + , A = N + Q * , lim = 1e9;
- struct operate
- {
- int x, y, k, cnt, num;
- operate(){}
- operate(int _x, int _y, int _k, int _cnt, int _num)
- {
- x = _x;
- y = _y;
- k = _k;
- cnt = _cnt;
- num = _num;
- }
- }a[A], q1[A], q2[A];
- int raw[N], ans[Q], sum[N];
- int n, m, len, l1, l2;
- void update(int x, int y)
- {
- for(int i = x; i <= n; i += (i & -i))
- sum[i] += y;
- }
- int query(int x)
- {
- int re = ;
- for(int i = x; i; i -= (i & -i))
- re += sum[i];
- return re;
- }
- void calc(int ll, int rr, int rawl, int mid)
- {
- for(int i = ll; i <= rr; ++i)
- {
- if(a[i].k)
- a[i].cnt = query(a[i].y) - query(a[i].x - );
- else if(a[i].y <= mid)
- update(a[i].x, a[i].cnt);
- }
- for(int i = ll; i <= rr; ++i)
- if(!a[i].k && a[i].y <= mid)
- update(a[i].x, -a[i].cnt);
- l1 = l2 = ;
- for(int i = ll; i <= rr; ++i)
- if(a[i].k)
- {
- if(a[i].k <= a[i].cnt)
- q1[++l1] = a[i];
- else
- {
- a[i].k -= a[i].cnt;
- q2[++l2] = a[i];
- }
- }
- else
- {
- if(a[i].y <= mid)
- q1[++l1] = a[i];
- else
- q2[++l2] = a[i];
- }
- int now = ll;
- for(int i = ; i <= l1; ++i)
- a[now++] = q1[i];
- for(int i = ; i <= l2; ++i)
- a[now++] = q2[i];
- }
- void divide(int ll, int rr, int rawl, int rawr)
- {
- if(rawl == rawr)
- {
- for(int i = ll; i <= rr; ++i)
- if(a[i].k)
- ans[a[i].num] = rawl;
- return;
- }
- int mid = (rawl + rawr) >> ;
- calc(ll, rr, rawl, mid);
- int tmp = l1;
- if(tmp)
- divide(ll, ll + tmp - , rawl, mid);
- if(ll + tmp <= rr)
- divide(ll + tmp, rr, mid + , rawr);
- }
- int main()
- {
- while(scanf("%d", &n) != EOF)
- {
- len = ;
- for(int i = ; i <= n; ++i)
- {
- scanf("%d", &raw[i]);
- a[++len] = operate(i, raw[i], , , );
- }
- scanf("%d", &m);
- int op, x, y, z;
- for(int i = ; i <= m; ++i)
- {
- scanf("%d", &op);
- if(op & )
- {
- scanf("%d%d", &x, &y);
- a[++len] = operate(x, raw[x], , -, );
- a[++len] = operate(x, y, , , );
- raw[x] = y;
- ans[i] = ;
- }
- else
- {
- scanf("%d%d%d", &x, &y, &z);
- a[++len] = operate(x, y, z, , i);
- }
- }
- divide(, len, , lim);
- for(int i = ; i <= m; ++i)
- if(ans[i])
- printf("%d\n", ans[i]);
- }
- return ;
- }
整体二分初探 两类区间第K大问题 poj2104 & hdu5412的更多相关文章
- 整体二分(模板) 求区间第k小
整体二分,将询问与初值一起放入一个结构体里,然后每次二分判断询问在哪边,树状数组维护,时间复杂度O((n+Q)lognlogMAX_a[i] 代码 #include<iostream> # ...
- 【BZOJ3110】【整体二分+树状数组区间修改/线段树】K大数查询
Description 有N个位置,M个操作.操作有两种,每次操作如果是1 a b c的形式表示在第a个位置到第b个位置,每个位置加入一个数c 如果是2 a b c形式,表示询问从第a个位置到第b个位 ...
- 【bzoj3110】[Zjoi2013]K大数查询 整体二分+树状数组区间修改
题目描述 有N个位置,M个操作.操作有两种,每次操作如果是1 a b c的形式表示在第a个位置到第b个位置,每个位置加入一个数c.如果是2 a b c形式,表示询问从第a个位置到第b个位置,第C大的数 ...
- [NBUT 1458 Teemo]区间第k大问题,划分树
裸的区间第k大问题,划分树搞起. #pragma comment(linker, "/STACK:10240000") #include <map> #include ...
- 区间第k大问题 权值线段树 hdu 5249
先说下权值线段树的概念吧 权值平均树 就是指区间维护值为这个区间内点出现次数和的线段树 用这个加权线段树 解决第k大问题就很方便了 int query(int l,int r,int rt,int k ...
- 算法笔记--CDQ分治 && 整体二分
参考:https://www.luogu.org/blog/Owencodeisking/post-xue-xi-bi-ji-cdq-fen-zhi-hu-zheng-ti-er-fen 前置技能:树 ...
- Dynamic Rankings(整体二分)
Dynamic Rankings(整体二分) 带修区间第k小.\(n,q\le 10^4\). 这次我们旧瓶装新酒,不用带修主席树.我们用整体二分!整体二分是啥东西呢? 二分答案可以解决一次询问的问题 ...
- BZOJ_3110_[Zjoi2013]K大数查询_整体二分+树状数组
BZOJ_3110_[Zjoi2013]K大数查询_整体二分+树状数组 Description 有N个位置,M个操作.操作有两种,每次操作如果是1 a b c的形式表示在第a个位置到第b个位置,每个位 ...
- 一篇自己都看不懂的CDQ分治&整体二分学习笔记
作为一个永不咕咕咕的博主,我来更笔记辣qaq CDQ分治 CDQ分治的思想还是比较简单的.它的基本流程是: \(1.\)将所有修改操作和查询操作按照时间顺序并在一起,形成一段序列.显然,会影响查询操作 ...
随机推荐
- 嗨翻C语言--这里没有蠢问题(一)
问:card_name[0]是什么意思?答:它是用户输入的第一个字符.如果用户输入了10,那么card_name[0]就将是1.问:总是得用/*和*/写注释吗?答:如果你的编译器支持C99标准,就可以 ...
- php批量POST修改
这是一个thinkphp中的批量修改的案例: 如需要删除多项,或者同时修改多项记录 要点: 前端表单中name要加[],如:<input type="hidden" name ...
- Scrapy 教程(十)-管道与数据库
Scrapy 框架将爬取的数据通过管道进行处理,即 pipelines.py 文件. 管道处理流程 一.定义 item item 表示的是数据结构,定义了数据包括哪些字段 class TianqiIt ...
- Windows Server 2016 安装.NET Framework 3.5 错误
WinServer2016默认安装.net 3.5会出现以下错误. 安装错误选择离线安装 Windows Server 2016离线安装.NET Framework 3.5方式有多种下面介绍2种: 一 ...
- C++ constexpr
1.constexpr 1.const与constexpr: const: 承若不改变这个值,主要用于说明接口,这样在把变量传入函数时就不必担心变量会在函数内被改变了,编译器负责确认并执行const的 ...
- oracle数据库ID自增长--序列
什么是序列?在mysql中有一个主键自动增长的id,例如:uid number primary key auto_increment;在oracle中序列就是类似于主键自动增长,两者功能是一样的,只是 ...
- 常用css相关笔记
最后一个css不加样式 .nav-sort li:not(:last-child) { border-bottom:#3e3e3e 1px solid; } 垂直居中 vertical-align: ...
- 牛客练习赛33 E tokitsukaze and Similar String (字符串哈希hash)
链接:https://ac.nowcoder.com/acm/contest/308/E 来源:牛客网 tokitsukaze and Similar String 时间限制:C/C++ 2秒,其他语 ...
- 2017 BJ ICPC 石子合并变种 向量基本功及分类考察
E 模拟 #include <bits/stdc++.h> #define PI acos(-1.0) #define mem(a,b) memset((a),b,sizeof(a)) # ...
- L3-016. 二叉搜索树的结构
二叉搜索树或者是一棵空树,或者是具有下列性质的二叉树: 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值:若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值:它的左.右子树也分别 ...