二分学习

咕咕咕

二分

 

一.二分查找

记住这两个模板

 

芝士向左找,也就是lower_bound

    while (l < r)
    {
        int mid = l + r >> 1;   //(l+r)/2
        if (a[mid] >= x)  r = mid;  
        else l = mid + 1;
    }

芝士向右找,也就是upper_bound

    while (l < r)
    {
        int mid = l + r + 1 >> 1;   //(l+r+1)/2
        if (a[mid] <= x)  l = mid;
        else r = mid - 1;
    }

 

 

记住这两个模板,你就会二分查找啦!!

没错,就这么简单

 

下面来看几道可爱的练习吧

A-B Problem

思考:

用每个数 + C 去找一下在序列中是否有这个数,如果有这个数就找出他第一次出现的位置和最后至此出现的位置 加上就ok

也就是说分别需要用一次 lower_bound 和 upper_bound 是不错的练习题呢

 

话说这道题为什么不能直接切,还要用二分,直接用一个数组统计一下每个数出现过的次数,如果有这个数就加一下统计一下不就出答案了吗

复制代码
#include<bits/stdc++.h>
using namespace std;
int main()
{
    int n, c, ans = 0;
    cin >> n >> c;
    int a[1000000];
    for (int i = 1; i <= n; i ++)
    {
        cin >> a[i];
    }
    sort(a + 1, a + 1 + n);//排序 
    for (int i = 1; i <= n; i ++)//遍历 
    {
        int l = i + 1, r = n;
        while (l < r)//往左找 
        {
            int mid = (l + r) >> 1;
            if (a[mid] >= a[i] + c) r = mid;//注意是小于等于不然你会见祖宗的 
            else l = mid + 1; 
        }
        if (a[l] == a[i] + c)//这个答案存在 
        {
            int cnm = l;
            l = cnm - 1, r = n;
            while (l < r)//往右找最后一个 
            {
                int mid = (l + r + 1) >> 1;
                if (a[mid] <= a[i] + c) l = mid;//同上
                else r = mid - 1; 
            }
            ans += (l - cnm + 1);
        }
        else
        {
            continue;
        }
     } 
    cout << ans << endl;
    return 0;
}
 
复制代码

是不是超级简单 那我们再来看下一道吧~

 

oj上没有这道题 所以我把题目放下来

题目内容: 现有 m(m <= 100000) 所学校,每所学校预计分数线是 ai(ai <= 10^6) 。有n(n <= 100000)位学生,估分分别为 bi(bi <= 10^6)。

根据 n 位学生的估分情况,分别给每位学生推荐一所学校,要求学校的预计分数线和学生的估分相差最小(可高可低,毕竟是估分嘛),这个最小值为不满意度。求所有学生不满意度和的最小值。

输入格式 第一行读入两个整数 m,n。m 表示学校数,n 表示学生数。

第二行共有 m 个数,表示 m 个学校的预计录取分数。第三行有 n 个数,表示 n 个学生的估分成绩。

输出格式 输出一行,为最小的不满度之和。

输入

4 3

513 598 567 689

500 600 550

 

输出

32

 

思考:

选择什么学校才是不满意值最小的?(这你应该知道吧) 用二分来找就行

最重要的是最低成绩和最高成绩的处理(具体看代码)

要求估分和分数线相差最小,那肯定分数线刚超过估分或者估分刚超过分数线。我们就转化为,求第一个大于等于估分的分数线的位置。

如此,这个位置的分数线或前一位置的分数线就是和估分相差最小的。

懂了吧

 

复制代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
long long a[N], x, sum, n, m;
int main() {
  cin >> n >> m;
  for (int i = 1; i <= n; i++) cin >> a[i];
  sort(a + 1, a + n + 1);
  a[0] = INT_MIN;
  a[n + 1] = INT_MAX;
  while (m--) {
    cin >> x;
    int l = 1, r = n + 1;
    while (l < r) {
      int mid = l + r >> 1;
      if (a[mid] >= x)
        r = mid;
      else
        l = mid + 1;
    }
    if (a[l] - x <= x - a[l - 1])
      sum += a[l] - x;
    else
      sum += x - a[l - 1];
  }
  cout << sum;
  return 0;
}
复制代码

 

课后练习:砍树

二分查找我们就学完啦!!!

 

二.二分答案

 

记住这两个模板

 

lower_bound

最小的最大值

  while (l < r)
    {
        int mid = l + r >> 1;   
        if (check(mid))  r = mid;    // check()判断mid是否满足性质
        else l = mid + 1;
    }
​

 

upper_bound

最大的最小值

 while (l < r)
    {
        int mid = l + r + 1 >> 1;
        if (check(mid))  l = mid;
        else r = mid - 1;
    }
​
 

如何判断一个题是不是用二分答案做的呢?

1、答案在一个区间内(一般情况下,区间会很大,暴力超时)

2、直接搜索不好搜,但是容易判断一个答案可行不可行

3、该区间对题目具有单调性,即:在区间中的值越大或越小,题目中的某个量对应增加或减少。

 

如果学会了这个那么你就基本已经掌握二分答案了!!!

对,就这么简单

 

来看几道练习题

木材加工

二分答案标志:小段木头的最大长度

 

思路:

小段木头可能的最小长度为 0 ,可能的最长长度为最长木棍的长度,判断条件:如果切成的小木棍大于等于要求的数量那就说明长度还可以再短一点,继续往大的找,所以是 upper_boud

 

 

完整代码:

复制代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
long long a[N], n, m, sum, maxx;
int check(int mid) {
  int sum = 0;
  for (int i = 1; i <= n; i++) {
    sum += a[i] / mid;
  }
  if (sum >= m) return 1;
  return 0;
}
int main() {
  cin >> n >> m;
  for (int i = 1; i <= n; i++) {
    cin >> a[i];
    sum += a[i];
    maxx = max(maxx, a[i]);
  }
  if (sum < m) {
    cout << 0;
    return 0;
  }
  int l = 1, r = maxx;
  while (l < r) {
    int mid = (l + r + 1) >> 1;
    if (check(mid))
      l = mid;
    else
      r = mid - 1;
  }
  cout << l;
  return 0;
}
​
 

 
复制代码

 

「NOIP2015」跳石头

二分答案标志:最短跳跃距离尽可能长

一看就是最大的最小值(upper_bound)

 

 

思路:枚举所有可能的跳跃长度,,计数可以被抽走的石头。如果可以被抽走的石头个数小于等于需要抽的m个了,就说明满足条件。

既然抽了小于M个都能满足剩下的石头中,两石头间的距离都大于等于mid了,那抽m个,更能满足

 

 

完整代码:

复制代码
#include <bits/stdc++.h>
using namespace std;
const int N = 50010;
int a[N], n, len, m, mina = 1e9 + 1, b[N];
int check(int mid) {
    int cnt = 0;
    int i = 0, now = 0;
    while (i < n + 1) {
        i++;
        if (a[i] - a[now] < mid) {
            cnt++;
        } else {
            now = i;
        }
    }
    if (cnt <= m) return 1;
    return 0;
}
int main() {
    cin >> len >> n >> m;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
        if (a[i] < mina) mina = a[i];
    }
    a[0] = 0, a[n + 1] = len;
​
    if (n == 0) {
        cout << len;
        return 0;
    }
    long long l = 1, r = 1e10;
    while (l < r) {
        int mid = l + r + 1 >> 1;
        if (check(mid))
            l = mid;
        else
            r = mid - 1;
    }
    cout << l;
    return 0;
}
复制代码

 

 

「POJ2456」Aggressive Cows

二分答案标志:所有牛中相邻的最近距离越大越好

最大的最小距离(upper_bound)

 

 

思路:和上面一样,直接找就行 很简单

 

完整代码:

复制代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1e7 + 1000;
int n, c;
int a[N];
int maxx = INT_MIN;
int check(int x) {
    int i = 1, j = 1, cnt = 0;
    while (j < n) {
        j++;
        if (a[j] - a[i] >= x) cnt++, i = j;
    }
    if (cnt + 1 >= c)
        return 1;
    else
        return 0;
}
​
int main() {
    cin >> n >> c;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
        maxx = max(a[i], maxx);
    }
    sort(a + 1, a + 1 + n);
    int l = 0, r = maxx;
    while (l < r) {  
        int mid = (l + r + 1) >> 1;
        if (check(mid) == true)
            l = mid;
        else
            r = mid - 1;
    }
    cout << l << endl;
​
    return 0;
}
复制代码

 

复制书稿

二分答案标志:使得复制时间最短(这算是个屁的标志)

 

思路:把所有可能的找一遍二分一边就行,注意要用lower_bound

 

完整代码:

 

复制代码
#include <iostream>
using namespace std;
const int N = 510;
int a[N], L[N], R[N];
int m, k, cnt;
int check(int sum) {
    int s = 0, cnt = 1;
    R[cnt] = m;
    for (int i = m; i; i--) {
        if (s + a[i] <= sum)
            s += a[i];
        else {
            L[cnt] = i + 1;
            cnt++;
            R[cnt] = i;
            s = a[i];
        }
    }
    L[cnt] = 1;
    if (cnt <= k) return 1;
    return 0;
}
int main() {
    scanf("%d%d", &m, &k);
    int l = 0, r = 1e9;
    for (int i = 1; i <= m; i++) {
        scanf("%d", &a[i]);
        l = max(l, a[i]);
    }
    while (l < r) {
        int mid = l + r >> 1;
        if (check(mid))
            r = mid;
        else
            l = mid + 1;
    }
    check(r);
    for (int i = k; i; i--) cout << L[i] << " " << R[i] << endl;
    return 0;
}
 
复制代码

 

好啦我相信你已经基本掌握二分啦!

 

再来几道综合练习题吧~

 

弹珠

二分答案标志:使得嫉妒值最小(这是个屁的标志)

 

思路:好像有个限制条件好像是每个孩子拿到的弹珠颜色必须相同,在check函数中特判一下就好了

 

完整代码:

复制代码
#include <bits/stdc++.h>
using namespace std;
int n, m, sum = 0;
int a[100000001];
int check(int x) {
    int num = 0;
    for (int i = 1; i <= m; i++) {
        num += a[i] / x;
        if (a[i] % x != 0) num++;
    }
    if (num <= n)
        return 1;
    else
        return 0;
}
int main() {
    cin >> n >> m;
    for (int i = 1; i <= m; i++) {
        cin >> a[i];
        sum += a[i];
    }
    int l = 1, r = sum;
    while (l < r) {
        int mid = (l + r) >> 1;
        if (check(mid))
            r = mid;
        else
            l = mid + 1;
    }
    cout << l << endl;
    return 0;
}
 
复制代码

 

 

升级版练习:Chocolate Eating S

二分答案标志:最不开心的一天尽可能的开心

思路:判断条件有坑,要自己好好想一想,然后再二分答案就ok

 

完整代码:

 

复制代码
#include <bits/stdc++.h>
using namespace std;
int n, d;
int a[100000];
int l, r;
int ans[100000];
int check(int x) {
    int sum = 0, ct = 0;
    for (int i = 1; i <= d; i++) { 
        while (sum < x) {
            sum += a[++ct];
            if (ct > n) return 0;
            if (x == l) ans[ct] = i;
        }
        sum >>= 1;
    }
    return 1;
}
int main() {
    cin >> n >> d;
    int sum = 0;
    for (int i = 1; i <= n; i++) cin >> a[i], sum += a[i];
    l = 0, r = sum;
    while (l < r) { 
        int mid = (l + r + 1) >> 1;
        if (check(mid))
            l = mid;
        else
            r = mid - 1;
    }
    cout << l << "\n";
    check(l);
    for (int i = 1; i <= n; i++)
        if (ans[i])
            cout << ans[i] << "\n";
        else
            cout << d << "\n"; 
    return 0;
}
 
复制代码

 

OK二分的所有内容你已经学完啦!

posted @   Aurkra  阅读(34)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
点击右上角即可分享
微信分享提示