审题:
本题需要我们找到可以将木头切割至少k段的单段长度最长值
思路:
方法一:暴力解法首先我们知道单段长度的最长值就是数组中数据的最大值max,所以我们可以遍历1~max的数据,将他们确定为l,然后计算出当前的切割段数,若大于等于k就记录下当前的l给answer变量,当遇到不满足大于等于k的情况,我们就直接退出循环,输出结果
优化1:逆序遍历1~max
由于我们是寻找满足段数大于等于k的最大l,而l越大段数其实越小,也就是说如果我们逆序遍历,段数是逐渐增加的,l是逐渐递减的,若我们遇到段数大于等于k,此时的l就是结果
时间复杂度:O(k*n)
因为我们外层遍历的是数组数据的最大值,而这个最大值最坏的情况是1e8,内层循环需要遍历数组计算段数,最坏情况进行1e5次,所以总共运行次数可能达到1e13,,一定超时
方法二:二分答案查找
其实我们的答案l的区间就是0到1e8(特殊处理了1cm的l也无法切割足够段数的情况,将0加入到答案区间),假设我们的答案为answer,那么answer自身以及其左边区域的l的段数k'都是大于等于k的(因为他们的l小,可划分的段数就多),同理answer右边区域的l的段数就都是小于k的。
此时就体现出这个区间的二段性,我们就可以使用二分查找的方法来提高效率了,而这里是对答案区间进行二分查找,所以又叫二分答案
第一步:二分查找答案区间
判断方法:
(1)当k' >= k:left = mid
(2)当k' < k: right = mid -1
而当left = mid的时候我们需要用向上取整的计算mid方法,防止死循环(出现在left与right相差偶数个数据的情况)
当left = mid + 1的时候用向下取整,防止跳过部分情况(出现在left和right相差奇数个数据的情况)
mid计算方法:(left+right+1)/2
k'计算方法:我们可以采用遍历数组a的数据来累加计算段数的方法
第二步:输出答案
答案就是left,因为答案一定在0到max之间,left和right最后相等就是找到答案了
解题:
#include<iostream> using namespace std; typedef long long ll; const int N = 1e5 + 10; int n, k; int a[N]; //计算l的可切割段数 ll calnum(ll l) { int cnt = 0; for (int i = 1; i <= n; i++) { cnt += a[i] / l; } return cnt; } int main() { cin >> n >> k; for (int i = 1; i <= n; i++) { cin >> a[i]; } int left = 0; int right = 1e8; ll mid = 0; while (left < right) { mid = (left + right + 1) / 2; if (calnum(mid) < k) { right = mid - 1; } else { left = mid; } } cout << left << endl; return 0; }