【237题】算法基础精选题单 刷题笔记DAY2二分

NC235558 牛可乐和魔法封印

链接:https://ac.nowcoder.com/acm/problem/235558
来源:牛客网

  1. 为了对付搜索不到的情况,人为在前后加入了边界(延长数据区间,使得必定有解),可以减少对边界的处理
  2. 二分循环退出后,l和r其实是相等的,取任意一个做答案即可

下面分别是加和不加边界的版本(注意,两种二分选取的边界不同,注意甄别)

#include <bits/stdc++.h>
using namespace std;
#define int long long

const int N = 2e5 + 10;
int n, m;
double s[N];
signed main()
{
  cin >> n;
  for (int i = 1; i <= n; i++)
    cin >> s[i];
  s[0] = -0x3f3f3f3f;
  s[n + 1] = 0x3f3f3f3f;
  int temp;
  cin >> temp;
  while (temp--)
  {
    int a, b;
    cin >> a >> b;
    int l = 0;
    int r = n + 1;
    while (l < r)
    {
      int mid = (l + r + 1) >> 1;
      if (s[mid] < a)
        l = mid;
      else
        r = mid - 1;
    }
    int ans1 = r;
    l = 0;
    r = n + 1;
    while (l < r)
    {
      int mid = (l + r) >> 1;
      if (s[mid] > b)
        r = mid;
      else
        l = mid + 1;
    }
    int ans2 = l;
    cout << ans2-ans1-1 << endl;
  }
}

不加

不加边界需要额外处理:1.区间在两侧且完全没有重合的情况

#include <bits/stdc++.h>
using namespace std;
#define int long long

const int N = 2e5 + 10;
int n, m;
double s[N];
signed main()
{
  cin >> n;
  for (int i = 0; i < n; i++)
    cin >> s[i];
  // s[0] = -0x3f3f3f3f;
  // s[n + 1] = 0x3f3f3f3f;
  int temp;
  cin >> temp;
  while (temp--)
  {
    int a, b;
    cin >> a >> b;
    if(b<s[0]||a>s[n-1]) 
    {
      cout<<0<<endl;continue;
    }
    int l = 0;
    int r = n - 1;
    while (l < r)
    {
      int mid = (l + r) >> 1;
      if (s[mid] < a)
        l = mid + 1;
      else
        r = mid;
    }
    int ans1 = l;
    l = 0;
    r = n - 1;
    while (l < r)
    {
      int mid = (l + r + 1) >> 1;
      if (s[mid] > b)
        r = mid - 1;
      else
        l = mid;
    }
    int ans2 = l;
      cout << ans2 - ans1 + 1 << endl;//对于+1这种在输出上对答案做出改变的,特别容易
      //出边界错误
  }
}

[USACO 2009 Dec S]Music Notes

链接:https://ac.nowcoder.com/acm/problem/24866
来源:牛客网

既然题目询问某时刻在的弹奏音符,可以用前缀和来记录每个音符的结束时间,然后二分搜索
大于等于当前询问的数字,索引即为正在弹奏的音符

#include <bits/stdc++.h>
using namespace std;
#define int long long

const int N = 2e5 + 10;
int n, m;
double s[N];
int sum[N];
signed main()
{
 cin>>n>>m;
 sum[0]=-1;
 for(int i=1;i<=n;i++)
 {cin>>s[i];sum[i]=sum[i-1]+s[i];}
  while(m--)
  {
    int now;cin>>now;
    int l=1,r=n;
    while(l<r)
    {
      int mid=(l+r)>>1;
      if(sum[mid]<now)l=mid+1;
      else r=mid;
    }
    cout<<l<<endl;
  }


    
}

[USACO 2016 Jan S]Angry Cows

链接:https://ac.nowcoder.com/acm/problem/24017
来源:牛客网

二分答案,由check函数去判断此答案是否满足条件,知道找到最小值,同时check里面也可以运用二分判断是否能满足条件

#include <iostream>
#include <algorithm>
using namespace std;
#define int long long

const int N = 2e5 + 10;
int n, m;
int s[N];
int sum[N];
int check(int mid)
{
  // cout << mid << endl;
  int ans = 0;
  int cnt = 0;
  int now = 0;
  for (int i = 0; i < n;)
  {
    int now = s[i] + mid;
    int l = i;
    int r = n;
    while (l < r)
    {
      int middie = (l + r) >> 1;
      if (s[middie] <= now)
        l = middie + 1;
      else
        r = middie;
    }
    ans++;
    if (ans > m)
      return false;
    i = l;
  }
  return true;
}
signed main()
{
  cin >> n >> m;
  for (int i = 0; i < n; i++)
    cin >> s[i];
  sort(s, s + n);
  int l = 1;
  int r = n;
  s[n] = 0x3f3f3f3f;//防止搜不到答案的边界处理问题
  while (l < r)
  {
    int mid = (l + r) >> 1;
    if (!check(mid * 2))
      l = mid + 1; // 不可以满足
    else
      r = mid;
    // cout << mid << endl;
  }
  cout << l << endl;
}

NC16462 [NOIP2015]跳石头

思路这里就引用洛谷题解上ShawnZhou大佬的思路吧

注意到题面:使得选手们在比赛过程中的最短跳跃距离尽可能长。如果题目规定了有“最大值最小”或者“
最小值最大”的东西,那么这个东西应该就满足二分答案的有界性(显然)和单调性(能看出来)。

使用一个check判断这个解是不是可行解。如果这个解是可行解,那么有可能会有比这更优的解,那么我们就去它的右边二分。为什么去右边?答案是,这个区间是递增的 ,而我们求的是最短跳跃距离的最大值,显然再右边的值肯定比左边大,那么我们就有可能找到比这更优的解,直到找不到,那么最后找到的解就有理由认为是区间内最优解。反过来,如果二分到的这个解是一个非法解,我们就不可能再去右边找了。因为性质,右边的解一定全都是非法解。那么我们就应该去左边找解。

然后模拟这个跳石头的过程即可

#include <iostream>
#include <algorithm>
using namespace std;
#define int long long

const int N = 2e5 + 10;
int len, n, m;
int s[N];
int d[N];
int sum[N];
int tempd[N];
int check(int mid)
{

  for (int i = 0; i <= n + 1; i++)//两种实现方式,main中我用一个数组去记录距离,其实没有必要//第二种实现会更好
    tempd[i] = d[i];
  // cout << mid << endl;
  int ans = 0;
  for (int i = 1; i <= n + 1; i++)
  {
    // cout << tempd[i] <<' ';
    if (tempd[i] < mid)
      tempd[i + 1] += tempd[i], ans++;
    if (ans > m)
      return false;
  }
  return true;
}

int check1(int mid)
{
  int ans = 0;
  int i = 0;
  int now = 0;
  while (i < n + 1)
  {
    i++;
    if (s[i] - s[now] < mid)
      ans++;
    else
      now = i;
    if (ans > m)
      return false;
  }
  return true;
}

signed main()
{
  cin >> len >> n >> m;
  s[n + 1] = len;
  for (int i = 1; i <= n; i++)
  {
    cin >> s[i];
    d[i] = s[i] - s[i - 1];
  }
  d[n + 1] = s[n + 1] - s[n];
  // sort(d+1,d+n+1);
  int l = 1;
  int r = len;
  while (l < r)
  {
    int mid = (l + r + 1) >> 1;
    if (!check(mid))
      r = mid - 1;
    else
      l = mid;
    // cout << endl;
  }
  cout << l << endl;
}

  1. 因此最后总结,对于可以二分答案解决的问题,其所求答案必定满足有界性和单调性,即必定存在一个边界y,使得对于x<y<z,设边界为y,那么左右区间必定分属不同性质,满足不同条件,且整个大区间整体单调,因此可以用二分去寻找边界。
  2. 对于二分经常容易出现的边界问题,可以人为去赋予一个边界避免问题出现(减少特判考虑)
  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值