P1873 [COCI 2011/2012 #5] EKO / 砍树

题目链接:

一、本题为什么能想到利用二分解决?

\(1.\) 有单调性

提高伐木机的高度,显然地,得到的木头会减少。

同样地,放低得到的木头会增多。

而正因为答案有单调性,所以我们可以使用二分。

\(2.\) 数据范围大

如果采用暴力枚举,时间复杂度为 \(O(n \cdot m)\) 会超时。用二分优化后时间复杂度约为 \(O(n \cdot log_2m)\) 最坏时间复杂度约为 \(3e^7\) 可以过。

二、二分答案

解题的时候往往会考虑枚举答案然后检验枚举的值是否正确。若满足单调性,则满足使用二分法的条件。把这里的枚举换成二分,就变成了「二分答案」。——OI Wiki

二分答案题的几个特征:

(1)求最大/最小值;

(2)答案离散(答案有有限种可能);

(3)容易判断答案是否正确

二分答案题的做法即是:

(1)确定答案区间;

(2)在保证答案在区间内的前提下,逐步缩小区间;

(3)当区间缩小到仅包含一个可能解时,该可能解即为答案。

这里主要要注意第(2)点,就是如何缩小区间。这是二分的精华所在,也是二分搜索程序如此难写的原因。

在本题中 \(\rm check\) 函数的作用是计算木材的总长度并判断是否满足需求。如果满足需求则查找是否有小的解(指砍树高度更高)否则向树高更低的区间查找。

下面给出 \(AC\) 代码。

#include <bits/stdc++.h>

using namespace std;
using LL = long long;

const int N = 1e6 + 5;

int a[N];

bool check(int a[], int x, int n, int m) {
	LL sum = 0;
	for (int i = 0; i < n; i++) {
		if (a[i] > x) sum += a[i] - x;
	}
	return sum >= m;
}//返回true表明:x代表的高度满足题意

int main()
{
	int n, m;
	scanf("%d%d", &n, &m);
	for (int i = 0; i < n; i++) scanf("%d", &a[i]);
	
	sort(a, a + n);
	
	int l = 0, r = a[n-1]-1;//在[0,最高的树高)进行枚举
	while (l < r) {
		int mid = l + r + 1 >> 1;
		if (check(a, mid, n, m)) l = mid;//满足题意则尽可能抬高锯子的高度
		else r = mid - 1;//否则说明木材量还不够,需要降低锯子的高度
	}
	printf("%d", l);
	return 0;
}
posted @ 2024-02-15 17:01  胖柚の工作室  阅读(110)  评论(0)    收藏  举报