首页 > 其他分享 >整体二分

整体二分

时间:2024-05-05 16:56:13浏览次数:19  
标签:二分 int mn 整体 tot Maxn mx

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++) {//答案全部为 l
			if(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;
}

标签:二分,int,mn,整体,tot,Maxn,mx
From: https://www.cnblogs.com/dzbblog/p/18173622

相关文章

  • 整体二分学习笔记
    1.简介在一些题目中,可能存在一些题目,对于每次询问直接二分可能会TLE,此时就要用到整体二分整体二分是一种离线的方法,适用于如下情况:询问答案具有可二分性修改对判定答案的贡献相互独立,修改之间互不影响效果修改如果对判定答案有贡献,则该贡献是一个确定的与判定标准无关......
  • 二分图染色
    二分图booldfs(intu,intc){ if(color[u]==c) returntrue; elseif(color[u]==3-c) returnfalse; color[u]=c; for(intv:graph[u]) if(!dfs(v,3-c))returnfalse; returntrue;}习题:P1330封锁阳光大学解题思路按照题目要求,每一条......
  • 注册表碎片整理是一种优化操作系统注册表的方法,旨在减少注册表文件的碎片化,从而提高系
    注册表碎片整理是一种优化操作系统注册表的方法,旨在减少注册表文件的碎片化,从而提高系统性能和响应速度。它通过重新整理和优化注册表文件的存储结构,以及压缩空闲空间等方式,来改善系统的整体表现。注册表是Windows操作系统中的核心组件之一,它存储了系统和安装的应用程序的配......
  • 整体二分
    整体二分动态排名每次二分复杂度\(O(n\logV)\),问题瓶颈在于有多次询问整体二分一种离线算法,将多个询问一起处理:条件问询可以二分修改之间互不影响修改对答案的贡献和判定次数,时间无关贡献满足结合律,交换律,可加性算法流程核心函数,处理一个区间的询问,他们的......
  • 二分的妙用
    数列分段SectionII链接:https://www.luogu.com.cn/problem/P1182题目描述对于给定的一个长度为\(N\)的正整数数列\(A_{1\simN}\),现要将其分成\(M\)(\(M\leqN\))段,并要求每段连续,且每段和的最大值最小。关于最大值最小:例如一数列\(4\2\4\5\1\)要分成\(3\)段。将......
  • 二分查找
    先给数组排序定义最小点left和最大点right取中间值作为cur循环判断,cur跟target谁更大若cur大了,则减小最大值right为cur-1;反之增加最小值为cur+1直到找到cur下标跟target一样大的情况,就可以返回cur了反之如果一直找不到,直到最小值left>最大值right了,就可以认为数组中没有这......
  • 利用二分法删除数组中元素
    二分法的思想主要是要设定起始值和终点值,计算中值,和给定值进行比较,如果大于给定值,则将中值作为终点值,否则作为起始值,重新计算中值。#include<stdio.h>intmain(){intarray[10]={1,2,3,5,8,15,20,30,100,200};intfirst=0,end=9,middle=(first+end)/2,num,i;s......
  • 二分查找
    1.0二分查找概念keywords:单调区间、最大化最小值(最小化最大值)、时间复杂度O(logn) 1.1二分模板模板来自于AK机大厂笔试星球。1.1.1在非递减数组中找到第一个≥x的数publicintlowerBound(int[]nums,intx){intl=0,r=nums.length-1;while(......
  • 二分查找的左闭右开和左闭右闭写法
    0.参考参考链接:二分查找的左闭右开和左闭右闭写法1.思路0.序言lower_bound查找的是升序序列中的第一个出现target的pos,区间应从右向左收缩。upper_bound查找的是升序序列中的最后一个出现target的pos,区间应从左向右收缩。主循环判断本质目的是为了确保整个区间能够被检索......
  • 2015 ACM ICPC Singapore Regional D(折半枚举+二分)
    D-AssociationofComputerMaintenance题意给定至多350个小于100的质数,对于所有质数之积k将它分解为两个数a和b使得a*b=k。输出最小的a+b,并对1e9+7取模分析首先考虑想如果想让a+b最小,即让abs(a-b)最小。随后根据限制条件k的因子数不超过1e10,容易想到将k拆分成k1和k2,此......