题意:
有一列数字,从大的往小的拿使最大最小值相差最小,每次只能拿一个,最多拿K次。
思路:
首先依据题意,求最小的极值确定下来是二分。刚开始想的是二分极值或最小值,但是判断函数十分难写。后来参考网上的思路对最大值和最小值分别进行二分,找到最大的最小值和最小的最大值,然后求出最小极值。很好的解题思路!但是注意两次二分的取值范围:对于最小值我们在0~sum/N之间二分,也就是说最小值一定不超过平均值(不可能超过平均值);对于最大值我们在(sum+N-1)/N~INF之间二分,最大值一定大于平均值。
上面的这句话是错误的,第一遍的时候这里没有想清楚,但是现在能够理解了。
更正为:对于最大值我们在(sum+N-1)/N~INF之间二分,最大值大于等于平均值。
考虑一种临界情况是sum刚好为n的整数倍,这个时候最大值和最小值应该都等于平均值,所以最大值的下边界也一定要从平均值开始。
起初一直在想为什么不能用(sum+n)/n,就是在于上面这种情况,如果sum为n的整数倍,利用(sum+n)/n这种计算就会得到错误的结果。
思路还是从操作系统的顺序存储多维数组下标的计算方法得来的,这种特别-1的情况就是为了防止这样的模的整数倍的情况。
另外还有一个是lower_bound和upper_bound的问题,二分的时候判定函数、left和right的更新以及终止条件一定要对应好,否则在边界时得不到想要的结果。对于二分的边界问题我现在理解还不是很好,接下来要继续做类似的题目去练习。
推荐一个很好的博客:点击打开链接
代码实现:
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#define LL long long
using namespace std;
const int MAX_N = 500050;
const int INF = 0x7f7f7f7f;
LL res;
int N,K;
int num[MAX_N];
LL check_upper(LL pos);
LL check_lower(LL pos);
int main()
{
while( scanf("%d%d",&N,&K) != EOF ){
LL sum = 0;
for( int i = 0; i < N; i++ ){
scanf("%d",&num[i]);
sum += num[i];
}
sort(num,num+N);
LL left = (sum+N-1)/N;
LL right = INF;
LL mid;
LL upper;
LL lower;
while(left <= right)
{
mid = (left + right) >> 1;
if( check_upper(mid) == 1 ){
right = mid - 1;
}
else{
left = mid + 1;
}
}
upper = left;
left = 0;
right = sum/N;
while( left <= right ){
mid = (left+right) >> 1;
if( check_lower(mid) == 1 ){
left = mid + 1;
}
else{
right = mid - 1;
}
}
lower = right;
res = upper-lower;
printf("%I64d\n",res);
}
return 0;
}
LL check_lower(LL pos){
LL lower = 0;
for( int i = 0; i < N; i++ ){
if( num[i] < pos ){
lower += pos-num[i];
}
}
if( lower<=K ){
return 1;
}
else{
return 0;
}
}
LL check_upper(LL pos){
LL upper = 0;
for( int i = 0; i < N; i++ ){
if( num[i] > pos ){
upper += num[i]-pos;
}
}
if( upper<=K ){
return 1;
}
else{
return 0;
}
}