二分搜索与二分答案

二分前提条件

  • 数组是有序的数组

  • 数组中无重复元素,否则查找的元素下标不是唯一的

二分答案

  • 二分答案时需要满足答案的有界性
  • 二分答案的思路:首先判断这个解是否为可行解,然后我们”认为“这个可行解的最优解,然后以这个可行解为标准去左(右)区间寻找最优解

时间复杂度

O(logN)

查找一个数的下标

左闭右闭区间 [l, r]

704. 二分查找 - 力扣(LeetCode)

class Solution {
	public:
		int search(vector<int>& nums, int target) {
			int l = 0, r = nums.size() - 1;
			while (l <= r) {	// 注意是 <=
				int mid = l + (r - l) / 2;	// 防止溢出,等价于 (l + r) / 2
				if (nums[mid] > target)
					r = mid - 1;	// 注意
				else if (nums[mid] < target)
					l = mid + 1;	// 注意
				else return mid;
			}
			return -1;
		}
};

左闭右开区间 [l, r)

class Solution {
	public:
		int search(vector<int>& nums, int target) {
			int l = 0, r = nums.size();	// 注意 r 的初始化
			while (l < r) {
				int mid = l + ((r - l ) >> 1);	// 注意 >> 1 要加括号
				if (nums[mid] > target)
					r = mid;	// 注意
				else if (nums[mid] < target)
					l = mid + 1;	// 注意
				else
					return mid;
			}
			return -1;
		}
};

查找第一次出现的位置

P2249

    int ans = -1;
    int l = 0, r = 7;
    while (l <= r)	// 注意 l == r 时还需要查询
    {
        int mid = l + (r - l >> 1);
        if (arr[mid] > num)
            r = mid - 1;
        else if (arr[mid] < num)
            l = mid + 1;
        else
        {
            ans = mid;
            r = mid - 1;
        }
    }

查找最后一次出现的位置

	int ans = -1;
    int l = 0, r = 7;
    while (l <= r)	// 注意 l == r 时还需要查询
    {
        int mid = l + (r - l >> 1);
        if (arr[mid] > num)
            r = mid - 1;
        else if (arr[mid] < num)
            l = mid + 1;
        else
        {
            ans = mid;
            l = mid + 1;
        }
    }

题目

P1102 A-B数对

题目描述

给出一串正整数数列以及一个正整数 C C C,要求计算出所有满足 A − B = C A - B = C AB=C 的数对的个数(不同位置的数字一样的数对算不同的数对)。

输入格式

输入共两行。

第一行,两个正整数 N , C N,C N,C

第二行, N N N 个正整数,作为要求处理的那串数。

输出格式

一行,表示该串正整数中包含的满足 A − B = C A - B = C AB=C 的数对的个数。

样例 #1

样例输入 #1

4 1
1 1 2 3

样例输出 #1

3

提示

对于 75 % 75\% 75% 的数据, 1 ≤ N ≤ 2000 1 \leq N \leq 2000 1N2000

对于 100 % 100\% 100% 的数据, 1 ≤ N ≤ 2 × 1 0 5 1 \leq N \leq 2 \times 10^5 1N2×105 0 ≤ a i < 2 30 0 \leq a_i <2^{30} 0ai<230 1 ≤ C < 2 30 1 \leq C < 2^{30} 1C<230

2017/4/29 新添数据两组

思路

A = B + C, 二分查找符合条件的A的个数,当然也可以直接用map解决这个问题

const int N = 2e5 + 2;

int arr[N];

signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);

    int n, c;
    cin >> n >> c;
    for (int i = 0; i < n; ++i)
        cin >> arr[i];
    sort(arr, arr + n);
    int ans = 0;
    for (int i = 0; i < n; ++i)
    {
        // 查找第一次和最后一次出现的位置 ans += ind2 - ind 1 + 1
        int num = c + arr[i];
        int ind1 = -1, ind2 = -1;
        int l = 0, r = n - 1;
        while (l <= r)
        {
            int mid = l + (r - l >> 1);
            if (arr[mid] > num)
                r = mid - 1;
            else if (arr[mid] < num)
                l = mid + 1;
            else
            {
                ind1 = mid;
                r = mid - 1;
            }
        }
        l = 0, r = n - 1;
        while (l <= r)
        {
            int mid = l + (r - l >> 1);
            if (arr[mid] > num)
                r = mid - 1;
            else if (arr[mid] < num)
                l = mid + 1;
            else
            {
                ind2 = mid;
                l = mid + 1;
            }
        }
        if (ind1 != -1)
            ans += ind2 - ind1 + 1;
    }
    cout << ans << endl;
    return 0;
}

也可以直接用STL中的二分函数

const int N = 2e5 + 2;

int arr[N];

signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);

    int n, c;
    cin >> n >> c;
    for (int i = 0; i < n; ++i)
    {
        cin >> arr[i];
    }
    sort(arr, arr + n);
    int ans = 0;
    for (int i = 0; i < n; ++i)
    {
        ans += (upper_bound(arr, arr + n, arr[i] + c) - lower_bound(arr, arr + n, arr[i] + c));
    }
    cout << ans << endl;
    return 0;
}

P1873 砍树

题目描述

伐木工人 Mirko 需要砍 M M M 米长的木材。对 Mirko 来说这是很简单的工作,因为他有一个漂亮的新伐木机,可以如野火一般砍伐森林。不过,Mirko 只被允许砍伐一排树。

Mirko 的伐木机工作流程如下:Mirko 设置一个高度参数 H H H(米),伐木机升起一个巨大的锯片到高度 H H H,并锯掉所有树比 H H H 高的部分(当然,树木不高于 H H H 米的部分保持不变)。Mirko 就得到树木被锯下的部分。例如,如果一排树的高度分别为 20 , 15 , 10 20,15,10 20,15,10 17 17 17,Mirko 把锯片升到 15 15 15 米的高度,切割后树木剩下的高度将是 15 , 15 , 10 15,15,10 15,15,10 15 15 15,而 Mirko 将从第 1 1 1 棵树得到 5 5 5 米,从第 4 4 4 棵树得到 2 2 2 米,共得到 7 7 7 米木材。

Mirko 非常关注生态保护,所以他不会砍掉过多的木材。这也是他尽可能高地设定伐木机锯片的原因。请帮助 Mirko 找到伐木机锯片的最大的整数高度 H H H,使得他能得到的木材至少为 M M M 米。换句话说,如果再升高 1 1 1 米,他将得不到 M M M 米木材。

输入格式

1 1 1 2 2 2 个整数 N N N M M M N N N 表示树木的数量, M M M 表示需要的木材总长度。

2 2 2 N N N 个整数表示每棵树的高度。

输出格式

1 1 1 个整数,表示锯片的最高高度。

样例 #1

样例输入 #1
4 7
20 15 10 17
样例输出 #1
15

样例 #2

样例输入 #2
5 20
4 42 40 26 46
样例输出 #2
36

提示

对于 100 % 100\% 100% 的测试数据, 1 ≤ N ≤ 1 0 6 1\le N\le10^6 1N106 1 ≤ M ≤ 2 × 1 0 9 1\le M\le2\times10^9 1M2×109,树的高度 ≤ 4 × 1 0 5 \le 4\times 10^5 4×105,所有树的高度总和 > M >M >M

思路

二分答案,即对最大高度4e5进行二分,对每一次二分得到的高度mid进行check,然后缩小区间,直到结束

int n, m;

bool check(int mid, vector<int> arr)
{
    int sum = 0;
    for (auto c : arr)
    {
        if (c > mid)
            sum += c - mid;
        else
            break;
    }
    return sum >= m;
}

signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);

    cin >> n >> m;
    vector<int> arr(n);
    for (int i = 0; i < n; ++i)
    {
        cin >> arr[i];
    }
    sort(arr.begin(), arr.end(),
         [](int a, int b)
         { return a > b; });
    int l = 0, r = 4e5;
    int ans = 0;
    while (l <= r)
    {
        int mid = l + (r - l >> 1);
        if (check(mid, arr))
        {
            ans = mid;
            l = mid + 1;
        }
        else
            r = mid - 1;
    }
    cout << ans << endl;
    return 0;
}

P2440 木材加工

题目背景

要保护环境

题目描述

木材厂有 n n n 根原木,现在想把这些木头切割成 k k k 段长度 l l l 的小段木头(木头有可能有剩余)。

当然,我们希望得到的小段木头越长越好,请求出 l l l 的最大值。

木头长度的单位是 cm \text{cm} cm,原木的长度都是正整数,我们要求切割得到的小段木头的长度也是正整数。

例如有两根原木长度分别为 11 11 11 21 21 21,要求切割成等长的 6 6 6 段,很明显能切割出来的小段木头长度最长为 5 5 5

输入格式

第一行是两个正整数 n , k n,k n,k,分别表示原木的数量,需要得到的小段的数量。

接下来 n n n 行,每行一个正整数 L i L_i Li,表示一根原木的长度。

输出格式

仅一行,即 l l l 的最大值。

如果连 1cm \text{1cm} 1cm 长的小段都切不出来,输出 0

样例 #1

样例输入 #1
3 7
232
124
456
样例输出 #1
114

提示

数据规模与约定

对于 100 % 100\% 100% 的数据,有 1 ≤ n ≤ 1 0 5 1\le n\le 10^5 1n105 1 ≤ k ≤ 1 0 8 1\le k\le 10^8 1k108 1 ≤ L i ≤ 1 0 8 ( i ∈ [ 1 , n ] ) 1\le L_i\le 10^8(i\in[1,n]) 1Li108(i[1,n])

思路

二分答案,即对最长的木头长度二分,对每一次二分得到的mid进行check,如果段数够就往右搜索,因为段数k是一定的,那么每段的长度mid越大,剩下的就越小

int n, k;

bool check(int mid, vector<int> arr)
{
    int s = 0;
    for (int c : arr)
    {
        s += c / mid;
    }
    return s >= k;
}

signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);

    cin >> n >> k;
    vector<int> arr(n);
    for (int i = 0; i < n; ++i)
    {
        cin >> arr[i];
    }
    sort(all(arr));
    int l = 1, r = arr.back();
    int ans = 0;
    while (l <= r)
    {
        int mid = l + (r - l >> 1);
        if (check(mid, arr))
        {
            l = mid + 1; // 一定是往右搜索,因为段数是一定的,mid越大,剩下的就越小
            ans = max(ans, mid);
        }
        else
            r = mid - 1;
    }
    cout << ans << endl;
    return 0;
}

P2678 跳石头

题目背景

NOIP2015 Day2T1

题目描述

一年一度的“跳石头”比赛又要开始了!

这项比赛将在一条笔直的河道中进行,河道中分布着一些巨大岩石。组委会已经选择好了两块岩石作为比赛起点和终点。在起点和终点之间,有 N N N 块岩石(不含起点和终点的岩石)。在比赛过程中,选手们将从起点出发,每一步跳向相邻的岩石,直至到达终点。

为了提高比赛难度,组委会计划移走一些岩石,使得选手们在比赛过程中的最短跳跃距离尽可能长。由于预算限制,组委会至多从起点和终点之间移走 M M M 块岩石(不能移走起点和终点的岩石)。

输入格式

第一行包含三个整数 L , N , M L,N,M L,N,M,分别表示起点到终点的距离,起点和终点之间的岩石数,以及组委会至多移走的岩石数。保证 L ≥ 1 L \geq 1 L1 N ≥ M ≥ 0 N \geq M \geq 0 NM0

接下来 N N N 行,每行一个整数,第 i i i 行的整数 D i   ( 0 < D i < L ) D_i\,( 0 < D_i < L) Di(0<Di<L), 表示第 i i i 块岩石与起点的距离。这些岩石按与起点距离从小到大的顺序给出,且不会有两个岩石出现在同一个位置。

输出格式

一个整数,即最短跳跃距离的最大值。

样例 #1

样例输入 #1
25 5 2 
2
11
14
17 
21
样例输出 #1
4

提示

输入输出样例 1 说明

将与起点距离为 2 2 2 14 14 14 的两个岩石移走后,最短的跳跃距离为 4 4 4(从与起点距离 17 17 17 的岩石跳到距离 21 21 21 的岩石,或者从距离 21 21 21 的岩石跳到终点)。

数据规模与约定

对于 20 % 20\% 20%的数据, 0 ≤ M ≤ N ≤ 10 0 \le M \le N \le 10 0MN10
对于 50 % 50\% 50% 的数据, 0 ≤ M ≤ N ≤ 100 0 \le M \le N \le 100 0MN100
对于 100 % 100\% 100% 的数据, 0 ≤ M ≤ N ≤ 50000 , 1 ≤ L ≤ 1 0 9 0 \le M \le N \le 50000,1 \le L \le 10^9 0MN50000,1L109

思路

二分答案,根据最多可以搬走的石头数量对每个mid进行check,然后向右搜索

int L, n, m;

bool check(int mid, vector<int> arr)
{
    int cnt = 0; // 为使mid成为可行解,需要搬走的石头数量
    for (int i = 1; i <= n + 1; i++)
    {
        if (arr[i] - arr[i - 1] < mid)
            cnt++, arr[i] = arr[i - 1];
    }
    if (cnt <= m)
        return true;
    return false;
}

signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);

    cin >> L >> n >> m;
    vector<int> arr(n + 2);
    for (int i = 1; i <= n; ++i)
    {
        cin >> arr[i];
    }
    arr.back() = L;

    int l = 0, r = L;
    int ans = 0;
    while (l <= r)
    {
        int mid = l + (r - l >> 1);
        if (check(mid, arr))
            l = mid + 1, ans = mid;
        else
            r = mid - 1;
    }
    cout << ans << endl;
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值