第十二周 acm刷题总结 二分

1 二分查找就不多赘述了,直接上代码吧。
都默认单调递增

int bin_search(int a*, int head, int tail, int val) {
	int l = head, r = tail;
	while(l <= r) {
		int mid = (l + r) >> 1;
		if (a[mid] == val) return mid;
		else if (a[mid] < val) l = mid + 1;
		else r = mid - 1;
	}
	return -1   //表示没找到
}

2 刚开始接触二分答案,初步认识是引用一位博主在别的算法里说过的“正难则反”思想,常规来讲我们习惯通过键访问值,但是二分答案让我们能够通过值访问键,前提是值随着键的单调变化也单调变化。对于二分答案,我有两套模板,对于一串数字:
1111111111000000

第一个模板是找第一个0,那么

int binary_search(int l, int r) {
	while (l != r) {//搜索区间不断剪短,等剪成一个点了,返回这个点就是答案。
		int mid = (l + r) >> 1;
		if (a[mid] == '0') {//如果二分点符合要求,那么他会变成右端点,因为答案可能是他,也可能是左面的点。
			r = mid;
		}
		else {//如果二分点不符合要求,那么他会连同左面区间一起被剪掉,因为答案只可能在他的右面。
			l = mid + 1;
		}
	}
	return l;
}

第二个模板是找最后一个1,代码:

int binary_search1(int* arr, int head, int tail, int x) {
        int l = head, r = tail;
        while (l != r) {
        int mid = ((l ^ r) >> 1) + (l & r) + 1;/*这个地方加1在除以2之前之后加都可以,他的作用是调整二分点的位置,防止搜索区间中途不再剪短。比如当l + 1 == r 的时候,把l更新为不加1的mid,就成了无效更新了。*/
        //下面不做解释了,和第一个模板同理
                if (arr[mid] == x) l = mid;
                else r = mid - 1;
        }
        //这里严谨一些,加上边界判断和万一找不到返回-1,遇到需要判断这东西的情况就加上。
    if (l == head && arr[l] != x) return -1;
        if (l == tail && arr[l] != x) return -1;
        return l;
}

上述模板可以用lower_bound()和upper_bound()代替,但是如果最核心的地方也就是那个if else语句不是现成的,而是需要临时生成的,就必须这样自己写二分了。不是,是凭我当前水平就只能自己写二分了。

对于“正难则反”, 整理两道类似的题:

Farmer John is an astounding accounting wizard and has realized he might run out of money to run the farm. He has already calculated and recorded the exact amount of money (1 ≤ moneyi ≤ 10,000) that he will need to spend each day over the next N (1 ≤ N ≤ 100,000) days.

FJ wants to create a budget for a sequential set of exactly M (1 ≤ M ≤ N) fiscal periods called "fajomonths". Each of these fajomonths contains a set of 1 or more consecutive days. Every day is contained in exactly one fajomonth.

FJ's goal is to arrange the fajomonths so as to minimize the expenses of the fajomonth with the highest spending and thus determine his monthly spending limit.

Input
Line 1: Two space-separated integers: N and M
Lines 2…N+1: Line i+1 contains the number of dollars Farmer John spends on the ith day
Output
Line 1: The smallest possible monthly limit Farmer John can afford to live with.
Sample Input

7 5
100
400
300
100
500
101
400

Sample Output

500

Hint
If Farmer John schedules the months so that the first two days are a month, the third and fourth are a month, and the last three are their own months, he spends at most $500 in any month. Any other method of scheduling gives a larger minimum monthly limit.

题意:输入n, m,再输入n 个元素,把这些元素顺序不变, 任意分割成m堆,求和最大的一堆的和的最小值。

思路:如果直接考虑怎么分,会发现这是个排列问题,随着数据量的增大,必定会超时,既然正面思考会超时,那么我们就先假设分完以后和最大的一堆和已经知道了,拿着这个最大和去求出最少能分成多少堆,由于这两个量的关系是单调的,负相关,所以正好迎合了二分答案的功能。而且,必须拿到这个假设的最大和,才能求出需要多少堆,所以要写二分了。

#include <iostream>
#include <algorithm>
using namespace std;

int a[100008], n, m;

int bin_search(int l, int r) {
        while (l != r) {  //二分答案
        int mid = (l + r) >> 1;
                int sum = 0;
                int cnt = 1;   
                for (int i = 1; i <= n; i++) {
                if (sum + a[i] <= mid) { 
                	sum += a[i];
                }
                    else {  //这一堆装不下了,必须另起一堆
                    cnt++;
                    sum = a[i];
                   }
                }
        if (cnt > m) { // 最少分成cnt堆,不合题意,所以解在它右面
                l = mid + 1;
                }
                else r = mid;  //符合题意,不过不一定是最小的最大和,再检查检查左面有没有更小的
        }
        return l;
}
int main() {
        ios::sync_with_stdio(false);
        cin.tie(0);
        cout.tie(0);
        cin >> n >> m;
        int mmax = 0;
        int sum = 0;
        for (int i = 1; i <= n; i++) {
        cin >> a[i];
                sum += a[i];
                if (mmax < a[i]) mmax = a[i];
        }
    cout << bin_search(mmax, sum) << endl;
        return 0;
}

还有一个求最大的最小值的,细节处理上我投入了更多:

Every year the cows hold an event featuring a peculiar version of hopscotch that involves carefully jumping from rock to rock in a river. The excitement takes place on a long, straight river with a rock at the start and another rock at the end, L units away from the start (1 ≤ L ≤ 1,000,000,000). Along the river between the starting and ending rocks, N (0 ≤ N ≤ 50,000) more rocks appear, each at an integral distance Di from the start (0 < Di < L).

To play the game, each cow in turn starts at the starting rock and tries to reach the finish at the ending rock, jumping only from rock to rock. Of course, less agile cows never make it to the final rock, ending up instead in the river.

Farmer John is proud of his cows and watches this event each year. But as time goes by, he tires of watching the timid cows of the other farmers limp across the short distances between rocks placed too closely together. He plans to remove several rocks in order to increase the shortest distance a cow will have to jump to reach the end. He knows he cannot remove the starting and ending rocks, but he calculates that he has enough resources to remove up to M rocks (0 ≤ M ≤ N).

FJ wants to know exactly how much he can increase the shortest distance *before* he starts removing the rocks. Help Farmer John determine the greatest possible shortest distance a cow has to jump after removing the optimal set of M rocks.

Input
Line 1: Three space-separated integers: L, N, and M
Lines 2…N+1: Each line contains a single integer indicating how far some rock is away from the starting rock. No two rocks share the same position.
Output
Line 1: A single integer that is the maximum of the shortest distance a cow has to jump after removing M rocks
Sample Input

25 5 2
2
14
11
21
17

Sample Output

4

Hint
Before removing any rocks, the shortest jump was a jump of 2 from 0 (the start) to 2. After removing the rocks at 2 and 14, the shortest required jump is a jump of 4 (from 17 to 21 or from 21 to 25).
题意:有n + 2块搭石, 给出河面上第一块搭石和最后一块搭石的距离, 中间搭石个数 n, 以及要挪走的搭石的个数m。再输入中间n 块搭石距离第一块搭石的距离。求挪动搭石后,搭石之间距离最小值的最大值。

思路:一样正难则反, 如果直接考虑挪走哪m块,会发现是个排列问题,太慢,那么就假设搭石之间的最小距离已经有了,那么可以用它求出最少需要挪走几块搭石,这两个量是单调关系, 最小距离越大,需要挪走的搭石越多。上代码:

#include <iostream>
#include <algorithm>
using namespace std;

int a[50008], L, n, m;

int bin_search(int l, int r) {
        while (l != r) {
        int mid = (l + r + 1) >> 1;  //第二套模板
            int cnt = 0;
            int pre = 1;
            for (int i = 1; i <= n + 1; i++) { //细节:虚构出a[n + 1],用来装最后一块搭石。
                if (a[i] - a[i - pre] >= mid) {   //此处pre的使用让人成就感爆棚,灵感来自于“削峰”。
                        pre = 1;
                        continue;
                 }
                  else {  //如果不引入pre, 而是直接把a[i]更新为a[i - 1],那此峰有削无回了,由此可见pre的好,抵得过一个备份数组了。
                        cnt++;
                        pre++;
                }
            }
        if (cnt > m) { //最少挪走cnt块,现在不合题意,只考虑左面区间。
                r = mid - 1;
                }
                else l = mid;   //符合题意,但不一定是最大的最小值,再检查一下右面区间。
        }
        return l;
}

int main() {
        ios::sync_with_stdio(false);
        cin.tie(0);
        cout.tie(0);
        cin >> L >> n >> m;
        int mmin = L;
        for (int i = 1; i <= n; i++) {
        cin >> a[i];
        }
        a[n + 1] = L;
        sort(a + 1, a + n + 1);
        for (int i = 1; i <= n + 1; i++) {
        mmin = min(mmin, a[i] - a[i - 1]);
        }
    cout << bin_search(mmin, L) << endl;
        return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值