题意:
给长度为n的数组,和数字k
进行k次操作,每次操作把n个数字中最大的减1,最小的加1
问k次操作之后最大值和最小值的差值是多少
数据范围:
n<=5e5,k<=1e9,a(i)<=1e9
思路:
第一次见到这种二分,好神奇。
如果k是无限的,那么最大值和最小值肯定接近平均值
但是k不是无限的,因此尽可能接近平均值
设n个数的和为sum
a[1]-a[n]从小到大排序
二分最大值:
最大值最小化
左边界为平均值sum/n,如果不整除则为sum/n+1
有边界为最大值a[n]
二分最小值:
最小值最大化
有边界为最小值a[1]
有边界为平均值sum/n.
ps:
这题二分边节的设置很重要
code:
//https://nanti.jisuanke.com/t/41406
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <vector>
using namespace std;
#define int long long
const int maxm=5e5+5;
int a[maxm];
int n,k;
bool check(int mid){//check最大
int sum=0;//需要的操作次数
int i=n;
while(i&&a[i]>mid){
sum+=a[i]-mid;
i--;
}
return sum<=k;
}
bool check2(int mid){//check最小
int sum=0;
int i=1;
while(i<=n&&a[i]<mid){
sum+=mid-a[i];
i++;
}
return sum<=k;
}
signed main(){
while(cin>>n>>k){
int sum=0;
for(int i=1;i<=n;i++){
cin>>a[i];
sum+=a[i];
}
int ave=sum/n;
sort(a+1,a+1+n);
int ans1=0;
int l=ave+(sum%n!=0),r=a[n];
while(l<=r){//二分最大
int mid=(l+r)/2;
if(check(mid)){
ans1=mid;
r=mid-1;
}else{
l=mid+1;
}
}
int ans2=0;
l=a[1],r=ave;
while(l<=r){//二分最小
int mid=(l+r)/2;
if(check2(mid)){
ans2=mid;
l=mid+1;
}else{
r=mid-1;
}
}
cout<<ans1-ans2<<endl;
}
return 0;
}