目录
本文提到过二分算法:不会的->戳我
题目描述
给定一个长度为 n 的数列 {an},和一个整数 k。每次操作,你可以在两种操作中选一种:
-
选择某个数 ai,将它减一。即令 ai=ai-1。
-
选择某两个数 ai 和 aj,将一个变为另一个。即令 ai=aj。
求最少多少次操作可以让数列的和小于等于 k。
输入格式
第一行一个整数 T,表示数据组数,
每组数据第一行两个整数 n 和 k,第二行 n 个整数a1到an。
输出格式
每组数据输出一行,代表答案。
样例输入:
4
1 10
20
2 69
6 9
7 8
1 2 1 3 1 2 1
10 1
1 2 3 1 2 6 1 6 8 10
样例输出:
10
0
2
7
数据规模 :
第一部分:主要思路
这个题我们可以用二分来做,我们二分操作次数,如果check(mid)返回1,那就往小的二分,否则就说明当前操作次数太少了,往大的二分。
第二部分:check()
其实check的实现也很好想。check的复杂度一般是O(n),在这里,我们可以枚举替换操作次数,mid是总操作次数,那么mid-替换操作次数就是删减操作了,在这里删减操作是给最小的数操作更好,所以我们sort一下取a[1]就行了,剩下的就是快速计算操作后整个序列的总和了。
第三部分:快速计算
我们知道,前缀和可以在O(1)时间内算出a1~ai的总和,所以我们维护一个前缀和就可以了,
因为替换了i个,所以总和是i*(a[1]-(mid-i))+sum[n-i]-(mid-i);(其中sum是前缀和数组)
注意事项:
-
十年OI一场空,不开long long见祖宗。
-
还有要排序。
- 每一次都要memset一下前缀和数组,全改成0。
第四部分:代码(代码中含注释)
第一种:
#include<bits/stdc++.h>
using namespace std;
long long a[200010];//读入数组
long long sum[200010];//前缀和数组
bool check(long long mid,int n,long long k)//开long long,check函数
{
for(long long i=0;i<n&&i<=mid;i++)//枚举替换次数,最多替换min(n,mid)个
{
long long res=mid-i;//删减操作次数
long long mi=a[1]-res;//删减后最小的数
long long ret=mi*(i)+sum[n-i]-res;//计算从a[1]到a[n]的总和
if(ret<=k)//总和小于k成立
{
return 1;
}
}
return 0;
}
int main()
{
int t;
cin>>t;
while(t--)
{
int n;
long long k;
cin>>n>>k;
memset(sum,0,sizeof(sum));//不要忘memset哦
for(int i=1;i<=n;i++)
{
cin>>a[i];//读入
}
sort(a+1,a+1+n);//sort排序
for(int i=1;i<=n;i++)
{
sum[i] = sum[i-1]+a[i];
}
long long l=0,r=LONG_LONG_MAX,ans=-1;//也可以改成r=k,我习惯写r=LONG_LONG_MAX
while(l<=r)//敲二分模板,有两种写法,这是第一种
{
long long mid=(l+r)/2;//计算mid
if(check(mid,n,k))//判断
{
r = mid-1;//缩小范围
ans=mid;//记录答案
}
else
{
l=mid+1;//扩大范围
}
}
cout<<ans<<'\n';//输出
}
return 0;
}
第二种:
#include<bits/stdc++.h>
using namespace std;
long long a[200010];//读入数组
long long sum[200010];//前缀和数组
bool check(long long mid,int n,long long k)//开long long,check函数
{
for(long long i=0;i<n&&i<=mid;i++)//枚举替换次数,最多替换min(n,mid)个
{
long long res=mid-i;//删减操作次数
long long mi=a[1]-res;//删减后最小的数
long long ret=mi*(i)+sum[n-i]-res;//计算从a[1]到a[n]的总和
if(ret<=k)//总和小于k成立
{
return 1;
}
}
return 0;
}
int main()
{
int t;
cin>>t;
while(t--)
{
int n;
long long k;
cin>>n>>k;
memset(sum,0,sizeof(sum));//不要忘memset哦
for(int i=1;i<=n;i++)
{
cin>>a[i];//读入
}
sort(a+1,a+1+n);//sort排序
for(int i=1;i<=n;i++)
{
sum[i] = sum[i-1]+a[i];
}
long long l=0,r=LONG_LONG_MAX;//也可以改成r=k,我习惯写r=LONG_LONG_MAX
while(l<r)//敲二分模板,有两种写法,这是第二种
{
long long mid=(l+r)/2;//计算mid
if(check(mid,n,k))//判断
{
r = mid;//缩小范围和记录答案合为一体
}
else
{
l=mid+1;//扩大范围
}
}
cout<<r<<'\n';//输出
}
return 0;
}