开宗明义:
二分真正的本质是:对于一个初始区间来说,如果存在某种性质,可以使得这个区间一分为二,其中一个区间满足这个性质,另一个区间不满足这个性质。这个时候就可以使用二分,来找到满足这个性质的区间的边界。
来源: zhuanlan.zhihu.com/p/509186088
其中值mid的计算方法 [L, R]的中间值mid,有多种计算方法:
mid = (L+R)/2
mid = (L+R)>>1
mid = L+(R-L)/2
从题目中去体会一下二分:
儿童节那天有K位小朋友到小明家做客。小明拿出了珍藏的巧克力招待小朋友们。
小明一共有N块巧克力,其中第i块是Hi x Wi的方格组成的长方形。
为了公平起见,小明需要从这 N 块巧克力中切出K块巧克力分给小朋友们。切出的巧克力需要满足:
- 形状是正方形,边长是整数
- 大小相同
例如一块6x5的巧克力可以切出6块2x2的巧克力或者2块3x3的巧克力。
当然小朋友们都希望得到的巧克力尽可能大,你能帮小Hi计算出最大的边长是多少么?
输入:
第一行包含两个整数N和K。(1 <= N, K <= 100000)
以下N行每行包含两个整数Hi和Wi。(1 <= Hi, Wi <= 100000)
输入保证每位小朋友至少能获得一块1x1的巧克力。
输出:
输出切出的正方形巧克力最大可能的边长。
输入eg.
2 10
6 5
5 6
输出eg.
2
解法1:
暴力解题(预估超时)
思想:
int h[100000],w[100000];
int n,k;
bool check(int d){ //检查够不够分(d为正方形边长,外用循环暴力加1试出来)
int num=0;
for(int i=0;i<n;i++) num += (h[i]/d)*(w[i]/d);
if(num>=k) return true; //够分
else return false; //不够分
}
外定义数组嵌套循环
满足方法d++,不满足返回d-1;
解法2:
经典应用:最小值最大化、最大值最小化
二分法的经典应用场景是“最小值最大化(最小值尽量大)”、“最大值最小化(最大值尽量小)”。
(1)最小值最大化
有n个牛棚,分布在一条直线上,有k头牛,给每头牛安排一个牛棚住,k<n。由于牛脾气很大,所以希望让牛之间尽量住得远一些。这个问题简化为:在一条直线上有n个点,选k个点,其中某两点之间的距离是所有距离中最小的,求解目标是让这个最小距离尽量大。这就是“最小值(两点间的最小距离)最大化”。
“牛棚问题”的求解可以用猜的方法。猜最小距离是D,看能不能在n个点中选k个,使得任意两点之间的距离≥D。如果可以,说明D是一个合法的距离。然后猜新的D,直到找到那个最大的合法的D。
具体操作是:从第1个点出发,然后逐个检查后面的点,第一个距离≥D的点,就是选中的第2点;然后从第2点出发,再选后面第一个距离第2点≥D的点,这是选中的第3点;…继续下去,直到检查完n个点。这一轮猜测的计算复杂度是O(n)的。
检查完n个点,如果选中的点的数量≥k,说明D猜小了,下次猜大点;如果选中的点的数量<k,说明D猜大了,下次猜小点。
如何猜D?简单的办法是从小到大一个个试,但是计算量太大了。
用二分法可以加速猜D的过程。设D的初值是一个极大的数,例如就是所有n点的总长度L。接下来的二分操作和前面的“猜数字游戏”一样,经过O(logL)次,就能确定D。
总计算量:一共O(logL)轮猜测,每一轮O(n),总计算量为O(nlogL)。
(2)最大值最小化
经典的例子是“序列划分”问题。有一个包含n个正整数的序列,把它划分成k个子序列,每个子序列是原数列的一个连续部分,第i个子序列的和为Si。在所有s中,有一个最大值。问如何划分,才能使最大的s最小?这就是“最大值(所有子序列和的最大值)最小化”。
例如序列{2, 2, 3, 4, 5, 1},将其划分成k=3个连续的子序列。下面举例2种分法:{(2, 2, 3)、(4, 5)、(1)},子序列和分别是7、9、1,最大值是9;{(2, 2, 3)、(4)、(5,1)},子序列和是7、4、6,最大值是7。第2种分法比第1种好。
仍然用猜的方法。在一次划分中猜一个x,对任意的Si都有Si ≤ x,也就是说,x是所有Si中的最大值。
如何找到这个x?简单的办法是枚举每一个x,用贪心法每次从左向右尽量多划分元素,Si不能超过x,划分的子序列个数为k个。但是枚举所有的x太耗时了。
用二分法可以加速猜x的过程。用二分法在[max, sum]范围内查找满足条件的x,其中max是序列中最大元素的值,sum是所有元素的和。
版权声明:上述引用为CSDN博主「罗勇军」的见解
第一次:开始时d的范围是1~D,试试中间值D/2,如果这个值大了,就把范围缩小为0 ~ D/2,如果这个值小了,就把范围缩小为D/2 ~ D;
第二次:取新的中间值D/4或3D/4, 再试…
......
直到找到合适的值为止。
import java.util.Scanner;
public class Main {
static int n, k;
static final int N = 100000;
static int[] h = new int[N];
static int[] w = new int[N];
static boolean check(int d) {
int num = 0;
for (int i = 0; i < n; i++)
num += (h[i] / d) * (w[i] / d);
if (num >= k) return true; // 够分
else return false; // 不够分
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
n = scanner.nextInt();
k = scanner.nextInt();
for (int i = 0; i < n; i++) {
h[i] = scanner.nextInt();
w[i] = scanner.nextInt();
}
int L = 1, R = N; // D的初值是R=100000
while (L < R) {
int mid = (L + R + 1) >> 1; // 除2,向右取整
if (check(mid)) L = mid; // 新的搜索区间是右半部分,R不变,调整L=mid
else R = mid - 1; // 新的搜索区间是左半部分,L不变,调整R=mid–1
}
System.out.println(L);
scanner.close();
}
}