題目
給一個長度爲n(n<=2e5)的數組a[](ai<=1e9)和一個數k(k<=n),
一次移動中,你可以選擇以下兩個操作中的一個,
①選擇當前數組中最小的元素x,令x=x+1
②選擇當前數組中最大的元素y,令y=y-1
求最小次數,使得a[]中有至少k個元素相等
思路來源
PinkRabbit兔隊的講解
https://www.bilibili.com/video/BV1Nz411b7ii
題解
此題的樣例比較具有提示性,提示該值既可能是從兩邊湊的,也可能只是從一邊湊的
思路還算比較清晰,主要是貪心的思想,分幾種情況討論,得到最優解
首先特判出現次數已經>=k的情形,否則把值按出現次數歸一下類
如果最終解可以是一個沒出現過的值,說明有一段值的操作次數均爲最小,總可以移動使之與某個端點重合
枚舉最終值ai的位置i,最優解必是以下情況中的其一
①把小於ai的所有值先升到ai-1,然後取ai還需的個數
②把大於ai的所有值先降到ai+1,然後取ai還需的個數
③把小於ai的所有值先升到ai-1,把大於ai的所有值先降到ai+1,然後取ai還需的個數
代碼
#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> P;
typedef long long ll;
typedef double db;
#define fi first
#define se second
#define pb push_back
#define vi vector<int>
#define SZ(x) (int)(x.size())
#define sci(x) scanf("%d",&(x))
#define all(v) (v).begin(),(v).end()
#define rep(i,a,b) for(int i=(a);i<=(b);++i)
#define per(i,a,b) for(int i=(a);i>=(b);--i)
ll modpow(ll x,ll n,ll mod){ll res=1;for(;n;n>>=1,x=x*x%mod)if(n&1)res=res*x%mod;return res;}
const db eps=1e-8,PI=acos(-1.0);
const int N=2e5+10,M=1e6+10,INF=0x3f3f3f3f,mod=1e9+7;//998244353
int n,k,a[N],id;
ll ans[N],v[N],c[N];
int main(){
int T=1;
// sci(T);
rep(ca,1,T){
sci(n),sci(k);
rep(i,1,n){
sci(a[i]);
}
sort(a+1,a+n+1);
map<int,int>mp;
rep(i,1,n){
mp[a[i]]++;
if(mp[a[i]]>=k)return 0*puts("0");
}
for(auto &x:mp){
id++;
v[id]=x.fi;
c[id]=x.se;
}
ll now=0,sum=0;//now個數 和爲sum
rep(i,1,id){
ans[i]+=(v[i]-1)*now-sum;//左側都升到v-1
sum+=v[i]*c[i];
now+=c[i];
}
now=0;sum=0;
per(i,id,1){
ans[i]+=sum-(v[i]+1)*now;//右側都降到v+1
sum+=v[i]*c[i];
now+=c[i];
}
rep(i,1,id){//最後k-c[i]個數從兩邊都取
ans[i]+=k-c[i];
}
ll ans1=0,ans2=0;
int pos=k;
while(pos+1<=n&&a[pos+1]==a[pos])pos++;
rep(i,1,pos){//必加到v-1 先都加到v 再退回去一部分
ans1+=a[pos]-a[i];
}
ans1-=pos-k;
pos=n+1-k;
while(pos-1>=1&&a[pos-1]==a[pos])pos--;
per(i,n,pos){
ans2+=a[i]-a[pos];
}
ans2-=(n+1-pos)-k;
// printf("ans1:%lld ans2:%lld\n",ans1,ans2);
printf("%lld\n",min(*min_element(ans+1,ans+id+1),min(ans1,ans2)));
}
return 0;
}