整体二分

news/2024/5/18 22:53:05

1 概念

在很多题目中,我们可以使用二分法来得出答案。但是如果说这一类题目有多次询问,并且多次询问分别二分会 TLE 时,我们就需要引入一个东西叫整体二分。

整体二分的主要思路就是将多个查询一起解决,因此它是一种离线算法。

整体二分的具体操作步骤如下:

首先记 \([l,r]\) 为答案的值域,\([L,R]\) 是答案的定义域。这代表着我们在求答案时考虑下标在 \([L,R]\) 上的操作,这当中的询问的答案都在 \([l,r]\)

首先我们现将所有操作按照时间轴存入数组,然后开始分治。在每一层分治中,我们利用一些东西统计当前查询的答案和 \(mid\) 的关系。

根据这个关系(小于等于 \(mid\) 和大于 \(mid\)),我们将操作序列分成两半,然后递归处理。

那么我们通过例题来具体了解整体二分的过程。

2 基础例题

2.1 静态全局第 k 小

在一个数列中查询第 \(k\) 小的数。

显然我们可以直接排序。那如果用二分呢?我们可以二分数字,然后查询这个数字的排名;这样看上去有点多此一举,我们看下一题。

在一个数列中多次查询第 \(k\) 小的数。

我们可以分开二分,但是也可以放在一起二分。

首先我们可以假设当前所有询问的答案都是 \(mid\),然后我们一次判断真正的答案与 \(mid\) 的关系。也就是应该小于等于 \(mid\) 还是大于 \(mid\),并分成两个部分。假如原先我们查询的值域为 \([l,r]\),那么现在两个区间的值域就是 \([l,mid],(r,mid]\)。在值域里继续二分查找,直到 \(l=r\)

可以理解为我们本来是一个一个二分,现在我们将他们放到一起同时做,这样可以省去当中重复运算的时间。

2.2 静态区间第 k 小

我们来看一道模板题:【模板】可持久化线段树 2。我们发现这是一道静态查询区间第 \(k\) 小问题,可以考虑整体二分。

我们在每一次二分中利用树状数组记录下当前区间内小于等于 \(mid\) 的数有哪些,用这个来帮助计算区间中小于等于指定数的数量。同时,为了提高效率,我们可以在统计时只对值域在 \([l,r]\) 之间的数进行统计,将他们单独拿出来之后在上面做二分。

时间复杂度 \(O(n\log ^2 n)\),比主席树 \(O(n\log n)\) 较劣。但是仍然可以过掉上面的模板题。

#include <bits/stdc++.h>using namespace std;typedef long long LL;
const int Maxn = 5e5 + 5;int n, m;
int mn = 2e9, mx;
struct node {int opt, l, r, k, id;
}q[Maxn], q1[Maxn], q2[Maxn];int tot = 0;
int ans[Maxn];struct BIT {int c[Maxn];int lowbit(int x) {return x & (-x);}void mdf(int x, int val) {for(int i = x; i <= n; i += lowbit(i)) {c[i] += val;}}int query(int x) {int sum = 0;for(int i = x; i; i -= lowbit(i)) {sum += c[i];}return sum;}
}B;void obs(int l, int r, int pl, int pr) {//[l,r] 是答案值域,[pl,pr] 是当前二分的查询区间if(pl > pr) return ;if(l == r) {for(int i = pl; i <= pr; i++) {//答案全部为 lif(q[i].opt == 2) {ans[q[i].id] = l;}}return ;}int mid = (l + r) >> 1, p1 = 0, p2 = 0;for(int i = pl; i <= pr; i++) {if(q[i].opt == 1) {//是修改操作if(q[i].k <= mid) {//与 mid 比较B.mdf(q[i].id, 1);//更新树状数组q1[++p1] = q[i];//比 mid 小的放到左半部分}else {q2[++p2] = q[i];//比 mid 大的放到右半部分}}else {int x = B.query(q[i].r) - B.query(q[i].l - 1);//查询当前区间内 mid 的排名if(q[i].k <= x) {q1[++p1] = q[i];//比 mid 小的放到左半部分} else {q[i].k -= x;//注意右半部分在计算之前要减掉左半部分的贡献q2[++p2] = q[i];//比 mid 大的放到右半部分}}}for(int i = 1; i <= p1; i++) {if(q1[i].opt == 1) {B.mdf(q1[i].id, -1);}}for(int i = 1; i <= p1; i++) {q[pl + i - 1] = q1[i];}for(int i = 1; i <= p2; i++) {q[pl + p1 + i - 1] = q2[i];}obs(l, mid, pl, pl + p1 - 1);obs(mid + 1, r, pl + p1, pr);//分治求解
}int main() {ios::sync_with_stdio(0);cin >> n >> m;for(int i = 1; i <= n; i++) {int p;cin >> p;mn = min(mn, p), mx = max(mx, p);q[++tot] = {1, -1, -1, p, i};}for(int i = 1; i <= m; i++) {int l, r, k;cin >> l >> r >> k;q[++tot] = {2, l, r, k, i};}obs(mn, mx, 1, tot);for(int i = 1; i <= m; i++) {cout << ans[i] << '\n';}return 0;
}

二维区间最小值例题:[国家集训队] 矩阵乘法。

2.3 带修区间第 k 小

例题:Dynamic Rankings。

我们发现这样一个问题:上面我们求静态区间第 k 小的时候已经将初始序列当做了插入操作,那么我们再做带修区间第 k 小的时候应该比较容易。

首先,一次修改操作可以看做是一次删除和一次插入操作组成的。而删除与查询操作本质上都是一样的,无非就是在树状数组上加一减一的区别。

代码与静态区间第 k 小的非常相似,如下:

#include <bits/stdc++.h>using namespace std;typedef long long LL;
const int Maxn = 5e5 + 5;int n, m, a[Maxn];
int mn = 2e9, mx;
struct node {int opt, l, r, k, id;
}q[Maxn], q1[Maxn], q2[Maxn];int tot, cnt;struct BIT {int c[Maxn];int lowbit(int x) {return x & (-x);}void mdf(int x, int val) {for(int i = x; i <= n; i += lowbit(i)) {c[i] += val;}}int query(int x) {int sum = 0;for(int i = x; i; i -= lowbit(i)) {sum += c[i];}return sum;}
}B;int ans[Maxn];void obs(int l, int r, int ql, int qr) {if(ql > qr) return ;if(l == r) {for(int i = ql; i <= qr; i++) {if(q[i].opt == 3) {ans[q[i].id] = l;}}return ;}int mid = (l + r) >> 1, p1 = 0, p2 = 0;for(int i = ql; i <= qr; i++) {if(q[i].opt == 3) {int x = B.query(q[i].r) - B.query(q[i].l - 1);if(q[i].k <= x) {q1[++p1] = q[i];}else {q[i].k -= x;q2[++p2] = q[i];}}else {if(q[i].k <= mid) {if(q[i].opt == 1) B.mdf(q[i].id, 1);else B.mdf(q[i].id, -1);q1[++p1] = q[i]; }else {q2[++p2] = q[i];}}}for(int i = 1; i <= p1; i++) {if(q1[i].opt == 1) B.mdf(q1[i].id, -1);else if(q1[i].opt == 2) B.mdf(q1[i].id, 1);}for(int i = 1; i <= p1; i++) {q[ql + i - 1] = q1[i]; }for(int i = 1; i <= p2; i++) {q[ql + p1 + i - 1] = q2[i];}obs(l, mid, ql, ql + p1 - 1);obs(mid + 1, r, ql + p1, qr);
}int main() {ios::sync_with_stdio(0);cin >> n >> m;for(int i = 1; i <= n; i++) {int p;cin >> p;mn = min(mn, p), mx = max(mx, p);a[i] = p;q[++tot] = {1, -1, -1, p, i};}for(int i = 1; i <= m; i++) {char opt;int l, r, k;cin >> opt >> l >> r;if(opt == 'C') {mn = min(mn, r), mx = max(mx, r);q[++tot] = {2, -1, -1, a[l], l};q[++tot] = {1, -1, -1, r, l};a[l] = r;}else {cin >> k;q[++tot] = {3, l, r, k, ++cnt};} }obs(mn, mx, 1, tot);for(int i = 1; i <= cnt; i++) {cout << ans[i] << '\n';}return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hjln.cn/news/28258.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

一些贪心的解题报告

一些贪心的解题报告 贪心题一般来说都是观察结论远简单于严谨证明,所以不会过多的去证明。 1.Tree compass 题目来源 codeforces div1 934 C https://codeforces.com/problemset/problem/1943/C 题面翻译 给定一棵节点数为\(n\)的树(\(1\le n \le 2\cdot 10^3\)),一开始节点都…

Ubuntu中CLion编译Geant4项目

围绕自带的/examples/basic/B1展开,其他项目相关操作类似。 成功安装Geant4后,首先验证B1示例能否正常运行,可以则进行下一步。 安装Clion。 进入B1示例,选择使用Clion打开目录中的CMakeLists.txt文件,以创建对应的项目(Project)。 进入项目后,直接Run该项目可能报如下…

Linux设置cp命令显示进度条

1、前言 实现原理: 重新安装cp、mv命令,显示进度条 测试环境:Centos7.6 查看当前系统下的coreutils工具包的版本 rpm -qa | grep -w coreutils当前版本8.22 2、下载coreutils安装包 不需要太新,8.32即可 wget http://ftp.gnu.org/gnu/coreutils/coreutils-8.32.tar.xz3、下…

浅拷贝与深拷贝

深拷贝,两个指针(PA,PB)指向同一块内存,PA变化,PB也跟着变化。 深拷贝,两个指针(PA,PB)指向不同内存,PA变化,PB不受影响。以Python写个demoimport copy# 原始列表 original_list = [[1, 2, 3], [4, 5, 6]]# 浅拷贝 shallow_copy = copy.copy(original_list)# 修改浅拷贝…

git 客户端使用

1.新建目录a,进入到a目录,鼠标右键Open git Bash here 2.克隆到本地:git clone git@124.221.230.131:/home/git/dataCollect.git 3.进入本地git仓库: cd dataCollect/ 4.查看分支:git branch 5.更新代码:git pull 6.进入本地git仓库,新建文件test.txt 7.提交代码到本地g…

overthewire - Bandit

随笔记 overthewire的密码会在一定周期更换。 Bandit Level 0 直接SSH连接2220端口 ssh -p 2220 bandit0@localhost 密码:bandit0ls 查看目录,看到readme,读取文件。 cat readme 获取bandit1密码 NH2SXQwcBdpmTEzi3bvBHMM9H66vVXjL Bandit Level 0 → Level 1 ls 查看目录下…

对C语言符号的一些冷门知识运用的剖析和总结

把概念和原理讲清楚、进阶、C语言符号符号 目录符号注释奇怪的注释C风格的注释无法嵌套一些特殊的注释注释的规则建议反斜杠\反斜杠有续行的作用,但要注意续行后不能添加空格回车也能起到换行的作用,那续行符的意义在哪?反斜杠的转义功能单引号和双引号字面值,字符串,字符,字…

k8s核心组件详解和分层架构

k8s核心组件master中的核心组件api-server(接口服务,基于rest风格开放k8s接口的服务) kube-controller-manager(管理各个类型的控制器,针对k8s中的各种资源进行管理)cloud-controller-manager(云控制管理器,第三方云平台提供的控制器,api对接管理功能) kube-scheduler…