博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
整体二分
阅读量:7226 次
发布时间:2019-06-29

本文共 3693 字,大约阅读时间需要 12 分钟。

Meaning

所谓整体二分,就是把所有询问一起二分,神仙至极,但是常数蛮大的 \(QwQ\)

能够处理支持离线,区间,可二分达到单询问 \(nlog\) 的这类询问。

一般而言复杂度 \(nlog^2\) ,因为修改询问线性查询的缘故,所以一般会带着一个树状数组。

General

一般来讲我们会把每个有权值的位置放到一个队列里,然后所有操作也放在这个队列里,但是放在权值之后。

然后我们二分一个值,将所有这个值下仍能合法的询问丢到右区间处理 (也就是它们的二分区间肯定都变成了 \([mid+1,r]\) ) ,同时将大于等于 \(mid\) 的权值和修改权值丢到右边去,因为它们会对且仅对右区间的询问产生贡献。

左区间同理。

当我们的二分区间 \(l==r\) 时,更新所有二分区间为这个的询问的答案。

大致就是一个函数 \(Solve(L,R,l,r)\) ,代表我们现在处理的是 \([L,R]\) 的操作或权值序列,它们对应的二分区间都是 \([l,r]\) 。每次求出有 \(len1\) 个操作和权值要丢到左区间,\(len2\) 个丢到右区间,那我们就拿出这些操作并重新摆放位置,确定那 \(len1\) 个操作都在 \([L,L+len1-1]\) 这段区间,且按照操作的时间顺序摆放,另外 \(len2\) 个操作同理。

那么我们的递归处理就是 \(Solve(L,L+len1-1,l,mid)\)\(Solve(L+len,R,mid+1,r)\) 了。

Example1 - 静态区间第 \(k\)

题目的话 \(......\) 洛谷的 就行。

这就是个很经典的整体二分问题了,对于每个区间,我们可以二分一个值 \(mid\) ,如果 \(mid\) 是满足小于 \(mid\) 的值有 \(k\) 个这个条件下最小的值,那么 \(mid\) 就是区间第 \(k\) 小了。

那么我们把每个位置的值先放到操作序列里,然后再按顺序把询问放到后面,就可以开始整体二分了。

对于一个值,我们直接判断是否 \(\le mid\) ,如果是的话,我们就将这个值的位置加上 \(1\) 的贡献,且它将会被丢到左区间,否则丢到右区间,对于一个询问,我们只需要查询询问区间内的贡献和是否 \(\le k\) 即可,如果是的话,就要丢到右区间,判断更大的值能不能行,否则丢到左区间,判断更小的值是否符合。

就是介么简单。

为保证篇幅不过长,只放动态区间第 \(k\) 小的代码。

不懂的话看看代码注释?

Example2 - 动态区间第 \(k\)

题目的话 \(......\) 就是了。

其实和静态区间第 \(k\) 小没什么差别,我们将一个修改操作分解成两个部分放入操作序列即可:第一个操作是删除修改前的权值,第二个操作放入修改后的权值。

那么我们在扫到权值的时候判断是删除还是加入并判断是否 \(\le mid\) 就好了。

Code

#include 
#include
using namespace std;int n, m, sum, cnt, a[200001], c[200001], tmp[400001], Ans[200001];char opt;struct node { int type, x, y, k, tmp, id;} q[400001], q1[400001], q2[400001];void add(int k, int x) { for (int i = k; i <= n; i += i & -i) c[i] += x;}int query(int k) { int ans = 0; for (int i = k; i > 0; i -= i & -i) ans += c[i]; return ans;}void Solve(int L, int R, int l, int r) { if (L > R) return; //如果没有东西了就返回 if (l == r) { for (int i = L; i <= R; i++) if (q[i].type == 3) Ans[q[i].id] = l; return; } //如果二分到底了就更新答案 int mid = (l + r) >> 1; for (int i = L; i <= R; i++) { if (q[i].type == 1 && q[i].y <= mid) add(q[i].x, 1); if (q[i].type == 2 && q[i].y <= mid) add(q[i].x, -1); if (q[i].type == 3) tmp[i] = query(q[i].y) - query(q[i].x - 1); } //按时间顺序扫一遍加入、删除操作并记录下每个询问内小于当前二分值的数的数量 int len1 = 0, len2 = 0; for (int i = L; i <= R; i++) { if (q[i].type == 1 && q[i].y <= mid) add(q[i].x, -1); if (q[i].type == 2 && q[i].y <= mid) add(q[i].x, 1); //清空树状数组 if (q[i].type == 3) { if (q[i].tmp + tmp[i] > q[i].k - 1) // q[i].tmp // 表示的是当前询问在上一个二分值下有多少个小于的数,因为我们丢进当前区间的权值已经不包括之前那些数了 q1[++len1] = q[i]; else { q[i].tmp += tmp[i]; q2[++len2] = q[i]; } } //将询问丢到相应区间 else if (q[i].y <= mid) q1[++len1] = q[i]; else q2[++len2] = q[i]; //将权值丢到相应区间 } for (int i = 1; i <= len1; i++) q[L + i - 1] = q1[i]; for (int i = 1; i <= len2; i++) q[L + len1 + i - 1] = q2[i]; //重新整理放置操作序列 Solve(L, L + len1 - 1, l, mid); Solve(L + len1, R, mid + 1, r); //递归处理}int main() { freopen("2617.in", "r", stdin); freopen("2617.out", "w", stdout); cin >> n >> m; for (int i = 1; i <= n; i++) { scanf("%d", &a[i]); sum++; q[sum].type = 1, q[sum].x = i, q[sum].y = a[i]; } // 第一种操作-加入,存入序列中的值 for (int i = 1; i <= m; i++) { scanf("%s", &opt); if (opt == 'Q') { q[++sum].id = ++cnt; scanf("%d%d%d", &q[sum].x, &q[sum].y, &q[sum].k); q[sum].type = 3; } //第三种操作-询问,存入询问区间,询问编号,询问值 else { int A, B; scanf("%d%d", &A, &B); sum++; q[sum].type = 2, q[sum].x = A, q[sum].y = a[A]; //第二种操作,删除 sum++; q[sum].type = 1, q[sum].x = A, q[sum].y = B; a[A] = B; } } Solve(1, sum, 0, 2e9); //整体二分,Start! for (int i = 1; i <= cnt; i++) printf("%d\n", Ans[i]);}

转载于:https://www.cnblogs.com/luoshuitianyi/p/10639275.html

你可能感兴趣的文章
80端口被Microsoft-HTTPAPI/2.0占用的解决办法
查看>>
无法抗拒Minecraft给予超高的自由度和探索-微访谈
查看>>
数据结构之串
查看>>
我的友情链接
查看>>
lvs+keepalived+nginx+tomcat高可用高性能集群部署
查看>>
实验:搭建主DNS服务器
查看>>
org.gjt.mm.mysql.Driver与com.mysql.jdbc.Driver区别
查看>>
部署exchange2010三合一:之五:功能测试
查看>>
nginx编译安装参数
查看>>
代码托管
查看>>
第一次给ThinkPHP5核心框架提pull request的完整过程
查看>>
U-Mail邮件系统何以誉为信息整合中转枢纽
查看>>
强大的vim配置文件,让编程更随意
查看>>
崛起于Springboot2.X之配置文件详解(10)
查看>>
定时执行程序-Quartz简单实例
查看>>
【CF 应用开发大赛】MyfCMS系统
查看>>
windows下kangle虚拟主机-架设java空间的教程及心得
查看>>
Discuz! X2.5:文件目录结构
查看>>
我的友情链接
查看>>
TCP/IP协议及首部初了解
查看>>