二分法学习
二分查找看起来确实是一个方便又快捷的方法,但是真正在使用的过程中我们会发现许多令人头疼的事——二分边界。
首先总结一下关于中间值取整问题:
- 首先,如果题目属于二分值越大越符合条件,即要求符合条件的最小值,那就是:
while(l<r){
mid=(l+r)/2; //或者mid=l+(r-l)/2;
if(check(mid))r=mid;
else l=mid+1;
}
return l;
较经典例题:乘法表中第k小数
- 反之,即 题目属于"二分值越小越符合条件",即要求符合条件的最大值,那就是:
while(l<r){
mid=(l+r+1)/2; //或者mid=l+(r-l+1)/2;
if(check(mid))l=mid;
else r=mid-1;
}
return l;
较经典例题:每个小孩最多能分到多少糖果
&emps;有关更新边界的解释,具体可查看某次洛谷日报
例题时间
e,g.1 数列分段
题目描述
对于给定的一个长度为N的正整数数列 \(A_{1\sim N}\),现要将其分成 \(M\)(\(M\leq N\))段,并要求每段连续,且每段和的最大值最小。
关于最大值最小:
例如一数列 \(4\ 2\ 4\ 5\ 1\) 要分成 \(3\) 段。
将其如下分段:
\[[4\ 2][4\ 5][1]
\]
第一段和为 \(6\),第 \(2\) 段和为 \(9\),第 \(3\) 段和为 \(1\),和最大值为 \(9\)。
将其如下分段:
\[[4][2\ 4][5\ 1]
\]
第一段和为 \(4\),第 \(2\) 段和为 \(6\),第 \(3\) 段和为 \(6\),和最大值为 \(6\)。
并且无论如何分段,最大值不会小于 \(6\)。
所以可以得到要将数列 \(4\ 2\ 4\ 5\ 1\) 要分成 \(3\) 段,每段和的最大值最小为 \(6\)。
分析
个人认为这是一道贪心与二分结合的不错的题,有一些绕
- 总体思路是完善
check
函数:即在最大和为mid
的情况下,计算最多能将数组分为多少组 - 如果分组数大于所需组数,说明最大值可以再大
- 相反,说明最大值要变小
int main() {
int n = read(), m = read();
vector<int> pre_sum(n + 1, 0);
int l = 1;
for (int i = 1; i <= n; i++) {
int tmp = read();
l = max(l, tmp);
pre_sum[i] = pre_sum[i - 1] + tmp;
}
long long r = pre_sum[n];
function<bool(int)>check = [&](int MAX)->bool {
int cnt = 0;
int cur = 1;
for (int i = 1; i <= n; i++) {
if (pre_sum[i] - pre_sum[cur-1] <= MAX) {
continue;
}
else {
cnt++;
cur = i;
}
}
return cnt < m;
};
while (l < r) {
int mid = l + (r - l) / 2;
if (check(mid))r = mid;
else l = mid + 1;
}
cout << l;
return 0;
}
e,g.2 Go快读板子使用
题目很简单,但这是我在洛谷上用Go过的第一道题。我发现Go的读入是真的太慢了!!!故记录下板子的使用。
package main
import (
"bufio"
"fmt"
"os"
"strconv"
)
var (
n, m int
nums []int
)
func main() {
defer ot.Flush()
n, m = rnI(), rnI()
nums = rsI(0, n)
var l, r int64 = 1, 2e9 + 1
for l < r {
var mid int64 = (l + r + 1) / 2
var sum int64 = 0
for i := 0; i < n; i++ {
if mid < int64(nums[i]) {
sum += int64(nums[i]) - mid
}
}
if sum >= int64(m) {
l = mid
} else {
r = mid - 1
}
}
fmt.Print(l)
}
var (
ot = bufio.NewWriterSize(os.Stdout, int(1e6))
in = bufio.NewScanner(os.Stdin)
)
func init() { in.Split(bufio.ScanWords); in.Buffer(make([]byte, 4096), int(1e9)) }
func rnS() string { in.Scan(); return in.Text() }
func rnI() int { i, _ := strconv.Atoi(rnS()); return i }
func rnF() float64 { f, _ := strconv.ParseFloat(rnS(), 64); return f }
func rsI(l, r int) []int {
t := make([]int, r)
for i := l; i < r; i++ {
t[i] = rnI()
}
return t
}