Codeforces Round #629 (Div. 3) F. Make k Equal(思维题-枚举+尺取+贪心)

题目

给一个长度为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;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小衣同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值