二分基础

二分

(类似于单调函数求零点)

二分查找

  • 在一个单调有序的集合中查找元素,每次将集合分为左右两部分,判断解在哪个部分中并调整集合上下界重复直到找到目标元素

题目:

给定一串n个单调递增的数,有q次询问>=x且<=y的数有多少个
数据规模:1n105 1q50000

手动实现
写法一
(注意下取整,避免死循环)

//找>=x的第一个位置
while(l < r){
mid = (l + r) / 2;
if(a[mid] >= x) r = mid;
else l = mid + 1;
}
//找<=x的最后一个位置
while (l < r) {
mid = (l + r + 1) / 2; //因为是下取整,所以要+1,避免出现l=l的情况 eg:3 3 3出现死循环
if(a[mid] <= x) l = mid;
else r = mid - 1;
}

写法二
(永远把mid丢掉,但是要用ans=min(ans,mid)记录答案);

//找>=x的第一个位置
while (l <= r) {
mid = (l + r) / 2;
if(a[mid] >= x) r = mid - 1; //ans是最后一次a[mid]>=x成立的mid,所以ans=r+1也就是l
else l = mid + 1;
}
//找<=x的最后个位置
while (l <= r) {
mid = (l + r) / 2;
if(a[mid] <= x) l = mid + 1; //ans是最后一次a[mid]<=x成立的mid,所以ans=l-1
else r = mid - 1;
}

Ending Edition

//找>=x的第一个位置
//最大值的最小值
while(l <= r) {
mid = (l + r) / 2;
if(a[mid] >= x){
r = mid - 1;
ans = min(ans, mid);
}else{
l = mid + 1;
}
}
//找<=x的最后一个位置
//最小值的最大值
while(l <= r) {
mid = (l + r) / 2;
if(a[mid] <= x){
l = mid + 1;
ans = min(ans, mid);
}else{
r = mid - 1;
}
}

C++STL的二分查找函数

  • binary_search 返回bool值,是否存在
  • lower_bound已排好序的序列a中利用二分搜索找出指向满足aikai最小的指针
  • upper_bound已排好序的序列a中利用二分搜索找出指向满足ai>kai最小的指针
    如果想要知道下标减去a即可
    Eg:lower_bound(a, a + 11, 55);

USACO music notes

手写二分

#include<bits/stdc++.h>
#define ios ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
using namespace std;
typedef long long LL;
const int N = 5e4 + 10;
int a[N], sum[N];
int n, q;
int main() {
ios;
cin >> n >> q;
for (int i = 1; i <= n; i++) {
cin >> a[i];
sum[i] = sum[i - 1] + a[i];
}
while (q--) {
int x;
cin >> x;
x++;
int l = 1, r = n;
while (l <= r) {
int mid = (l + r) / 2;
if (sum[mid] >= x) r = mid - 1;
else l = mid + 1;
}
cout << l << "\n";
}
return 0;
}

STL

#include<bits/stdc++.h>
#define ios ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
using namespace std;
typedef long long LL;
const int N = 5e4 + 10;
int a[N], sum[N];
int n, q;
int main() {
ios;
cin >> n >> q;
for (int i = 1; i <= n; i++) {
cin >> a[i];
sum[i] = sum[i - 1] + a[i];
}
while (q--) {
int x;
cin >> x;
x++;
int ans = lower_bound(sum + 1, sum + n + 1, x) - sum;
cout << ans << "\n";
}
return 0;
}

二分答案+检验

二分枚举+检验: 将求解类问题转化为验证类问题

牛棚

题目描述:
有N个牛棚在x轴上,已知他们的坐标。有C只奶牛,每只都必须安排在一个牛棚里,一个牛棚只能容纳一只,但是他们会互相攻击,所以要求距离最近的两个牛棚间的距离最大
(2 <= N <= 100000, 0 <= xi <= 1000000000, 2 <= C <= N)

分析:

  • 我们可以假设距离最近的两个牛棚间的距离为x,判断这个x是否可行
  • 先把xi排序
  • 对于一个解m,我们验证m是否可行
  • 第一个点放置牛,从左向右O(n)扫描一遍牛棚,第i个点如果和上一个放置点的距离m,则在第i个点放置一头牛,统计总放置的牛数量是否C
#include<bits/stdc++.h>
#define ios ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
using namespace std;
typedef long long LL;
const int N = 1e5 + 10;
LL a[N];
int n, c;
bool check(int x) {
int la = a[1];
int cnt = 1;
for (int i = 2; i <= n; i++) {
if (a[i] - la >= x) {
la = a[i];
cnt++;
}
if (cnt >= c) return true;
}
return false;
}
int main() {
ios;
while (scanf("%d%d", &n, &c) != EOF) {
for (int i = 1; i <= n; i++) {
scanf("%lld", & a[i]);
}
sort(a + 1, a + n + 1);
int mid, l = 1, r = a[n];
while (l <= r) {
mid = (l + r) >> 1;
if (check(mid)) l = mid + 1;
else r = mid - 1;
}
cout << r << endl;
}
return 0;
}

Drying(poj)

题目描述:
有n件衣服需要晾干,每件含水量ai,每件衣服每分钟自然干1单位的水,每分钟可以对其中任意一件使用吹风机,其可以减少k的水,求晾干所有衣服的最少时间

  • 1 n105
  • 1 ai109
  • 1 k109
    分析:
  • 设某次二分出的一个值是mid
  • 对于一件aimid衣服,直接晾干即可;
  • 对于一件ai>mid的衣服,最少的用时是用吹风机一段时间,晾干一段时间,设这两段时间分别是x1x2,那么有mid=x1+x2, aikx1+x2,解得x1(aimid)/(k1),所以对(aimid)/(k1)向上取整就是该件衣服的最少用时。
  • 我们把所有的使用吹风机时间加起来,这个总和应该mid
    (太毒瘤了这题)
//poj不能用万能头
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
using namespace std;
typedef long long LL;
const int N = 1e5 + 10;
int a[N];
int n, k;
bool judge(int x) {
LL sum = 0;
for (int i = 1; i <= n; i++) {
if (a[i] <= x) continue;
sum += ceil((a[i] - x) * 1.0 / (k - 1)); //上取整, 毒瘤点:1.吹的同时也在自然晾干 2.k=1时除0
}
return sum <= x;
}
int main() {
scanf("%d", &n);
int l = 1, r = 0;
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
r = max(r, a[i]);
}
cin >> k;
if (k == 1) {
printf("%d", r);
return 0;
}
while (l <= r) {
int mid = (l + r) / 2;
if (judge(mid))r = mid - 1;
else l = mid + 1;
}
printf("%d", r + 1);
return 0;
}

Chocolate Eating(USACO)

分析:

  • 二分最小的快乐值
  • 如果今天心情达不到快乐值,就吃巧克力达到为止
  • 巧克力不够则目标值过大

4 Values whose Sum is 0(poj)

分析:
既然有四列,那么我们可以分别计算前两列和后两列的和(只需要2n2次运算),然后对后两列的和排序,那么我们对于每一个前两列的和都可以二分找到后两列的和中与之相加为0的个数,这样复杂度就是O(log(n)n2),可以过。

#include<bits/stdc++.h>
#define ios ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
using namespace std;
typedef long long LL;
const int N = 4010;
int n, a[6][N];
int p[N * N], q[N * N];
void pre(int p[], int x, int y) {
int cnt = 0;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
p[++cnt] = a[x][i] + a[y][j];
}
}
}
int main() {
ios;
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[1][i] >> a[2][i] >> a[3][i] >> a[4][i];
}
pre(p, 1, 2);
pre(q, 3, 4);
n = n * n;
sort(q + 1, q + 1 + n);
LL cnt = 0;
for (int i = 1; i <= n; i++) {
cnt += upper_bound(q + 1, q + 1 + n, -p[i]) - lower_bound(q + 1, q + 1 + n, -p[i]);
}
cout << cnt << endl;
return 0;
}

扑克牌

分析:
直接贪心很难构造,又因为是满足单调性的(套数>答案的都不可行,答案的都可行),我们直接二分答案考虑验证。

  • 如果当前待验证的数字x(即判断当前手上的牌够不够组成x套),首先,张数x的牌只需要每一套用一张就行,< x的牌差几张就需要补几张joker,于是我们可以计算出究竟要补几张joker
    • 如果最后需要补的joker数比手上有的joker数多,说明joker不够,不行。
    • 如果最后需要补的joker数比x还大,说明必然有两个joker在一套牌里,也不行,其他情况都是可以的。

借教室

分析:
二分第一个需要修改的申请人是第几个,然后用差分维护出来前面的这些人申请完了之后每天剩下多少个教室,如果有负的,说明之前已经有订单无法满足了,减少右界,否则增大左界。

#include<bits/stdc++.h>
#define ios ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
using namespace std;
typedef long long LL;
const int N = 1e6 + 10;
int n, m;
int r[N];
struct ty {
int d, s, j;
} a[N];
LL delta[N];
bool check (int x) {
for (int i = 1; i <= n; i++) {
delta[i] = r[i] - r[i - 1];
}
for (int i = 1; i <= x; i++) {
delta[a[i].s] -= a[i].d;
delta[a[i].j + 1] += a[i].d;
}
LL sum = 0;
for (int i = 1; i <= n; i++) {
sum += delta[i];
if (sum < 0) {
return 0;
}
}
return 1;
}
int main() {
ios;
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> r[i];
}
for (int i = 1; i <= m; i++) {
cin >> a[i].d >> a[i].s >> a[i].j;
}
int l = 0, r = m;
while (l <= r) {
int mid = (l + r) / 2;
if (check(mid)) l = mid + 1; //l-1最后一次能借,所以不能借是l
else r = mid - 1;
}
if (l > m) cout << 0; //完成m件订单后l不是=m,而是大于m,因为l-1是最后一次能借,l-1等于m
else cout << "-1" << endl << l << endl;
return 0;
}

总结

解决最大值最小or最小值最大的常见方法

  • 贪心
  • 二分
  • 动态规划
posted @   csai_H  阅读(14)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!
点击右上角即可分享
微信分享提示