[ exttt{Description}
]
给一个 (n) 和 (k) ,以及长度为 (n) 的序列 (a) 。
有两种操作:
- 让数组中其中一个最小值的值加一。
- 让数组中其中一个最大值的值减一。
问:最少几次操作,可以使得数组中至少有 (k) 个数相等。
[ exttt{Solution}
]
- 首先有一个
非常非常非常显然的结论:
定存在最优解,满足最后相等的 (k) 个数的值,为 (a) 中出现过的值。
-
为了方便做题,我们将序列 (a) 打包成若干个二元组 ((num,cnt)) ,表示值为 (num) 的数在 (a) 中有 (cnt) 个。
-
此时我们注意到:对于任意数 (x) ,若想通过【最小数加一】这个操作使得小于 (x) 的数变成 (x) ,必须要使得所有小于 (x) 的所有数先变成 (x-1) ,再进行一次该操作,才能使得一个小于 (x) 的数变成 (x) 。
-
【最大数减一】同理。
-
于是,我们处理出 " 将小于 (num[i]) 的所有数都变成 (num[i]-1) 的最少步数 " 和 " 将大于 (num[i]) 的所有数都变成 (num[i]+1) 的最少步数 " ,分别记作 (pre[i]) 和 (suf[i]) ,处理出 " 小于 (num[i]) 的数的个数 " 和 " 大于 (num[i]) 的数的个数 " ,分别记作 (pcnt[i]) 和 (scnt[i]) 。
-
接下来,枚举每一个 (num[i]) ,计算 (num[i]) 作为最后相等的 (k) 个数的值情况下的最少步数,取一个最小值即可求出答案。
-
不妨记 (res=k-cnt[i]) 。
-
首先,若有 (res leq 0) ,则说明原数组已经有至少 (k) 个数相等了。
-
否则我们还需要凑够 (res) 个 (num[i]) ,才可以使得该情况下有 (k) 个 (num[i]) ,对于每个 (num[i]) ,有三种情况:
- 用【最小数加一】和【最大数减一】两个操作使得有 (k) 个 (num[i]) 相等,此时最少步数就是 (pre[i]+suf[i]+res)。
- 用【最小数加一】操作使得有 (k) 个 (num[i]) 相等,若 (pcnt[i]geq res) ,此时最少步数就是 (pre[i]+res) 。
- 用【最大数减一】操作使得有 (k) 个 (num[i]) 相等,若 (scnt[i]geq res) ,此时最少步数就是 (suf[i]+res) 。
- 分情况讨论一下即可。
- (mathcal{O(n log n)}) ,评测链接 。
[ exttt{Code}
]
#include<cstdio>
#include<algorithm>
using namespace std;
namespace IO
{
static char buf[1<<20],*fs,*ft;
inline char gc()
{
if(fs==ft)
{
ft=(fs=buf)+fread(buf,1,1<<20,stdin);
if(fs==ft)return EOF;
}
return *fs++;
}
#define gc() getchar()
inline int read()
{
int x=0,f=1;char s=gc();
while(s<'0'||s>'9'){if(s=='-')f=-f;s=gc();}
while(s>='0'&&s<='9'){x=x*10+s-'0';s=gc();}
return x*f;
}
}using IO::read;
const int N=200100;
int n,k;
int a[N];
int m,num[N],cnt[N];
long long pre[N],suf[N];
long long pcnt[N],scnt[N];
long long ans=1e18;
int main()
{
n=read(),k=read();
for(int i=1;i<=n;i++)
a[i]=read();
sort(a+1,a+1+n);
int S=0;
for(int i=1;i<=n;i++)
{
S++;
if(a[i]!=a[i+1])
{
m++;
num[m]=a[i],cnt[m]=S;
S=0;
}
}
for(int i=2;i<=m;i++)
{
pre[i]=pre[i-1]+pcnt[i-1]*(num[i]-num[i-1])+cnt[i-1]*(num[i]-num[i-1]-1);
pcnt[i]=pcnt[i-1]+cnt[i-1];
}
for(int i=m-1;i>=1;i--)
{
suf[i]=suf[i+1]+scnt[i+1]*(num[i+1]-num[i])+cnt[i+1]*(num[i+1]-num[i]-1);
scnt[i]=scnt[i+1]+cnt[i+1];
}
for(int i=1;i<=m;i++)
{
int res=k-cnt[i];
if(res<=0)
{
puts("0");
return 0;
}
if(i>1&&i<m)
ans=min(ans,pre[i]+suf[i]+res);
if(i>1&&pcnt[i]>=res)
ans=min(ans,pre[i]+res);
if(i<m&&scnt[i]>=res)
ans=min(ans,suf[i]+res);
}
printf("%lld
",ans);
return 0;
}
[ exttt{Thanks} exttt{for} exttt{watching}
]