CF-1328 F. Make k Equal
F. Make k Equal
题意
长度为n的序列,每次可以选择一个最大的数字将其减一或者选择一个最小的数字将其加一,问最少操作多少次可以使得序列中至少存在 k 个一样的数字
分析
官方题解:http://codeforces.com/blog/entry/75246
可以想到最后一样的数字,一定是在原序列里面出现的,所以将原数组离散化之后,枚举最后一样的数字,并努力把它凑够 k 个。如何凑?借助左侧或者右侧的数字。只要借助了某侧的数字,那么这一侧全部的数字都要先挪动它旁边的那个位置,然后再按照需要搬到当前这个位置上来。
所以最后只会有两种方案:优先选左侧或者优先选右侧,我们都试一下就好了,结果取最小(计算细节可以参考代码以及官方题解)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
#define dbg(x...) do { cout << "\033[32;1m" << #x <<" -> "; err(x); } while (0)
void err() { cout << "\033[39;0m" << endl; }
template<class T, class... Ts> void err(const T& arg,const Ts&... args) { cout << arg << " "; err(args...); }
const int N = 200000 + 5;
int n, k, a[N];
vector<int> v;
ll cnt[N], sum[N];
ll pre[N];
int main(){
scanf("%d%d", &n, &k);
for(int i=1;i<=n;i++){
scanf("%d", &a[i]);
v.push_back(a[i]);
}
v.push_back(0);
sort(v.begin(),v.end());v.erase(unique(v.begin(),v.end()),v.end());
for(int i=1;i<=n;i++){
int id = lower_bound(v.begin(),v.end(),a[i]) - v.begin();
cnt[id] ++;
}
n = v.size()-1;
for(int i=1;i<=n;i++) {
sum[i] = sum[i-1] + cnt[i];
pre[i] = pre[i-1] + cnt[i] * v[i];
}
ll res = pre[n];
for(int i=1;i<=n;i++){
ll need = max(0ll, k - cnt[i]);
// 先左后右
{
ll tmp = 0;
int lnum = min(need, sum[i-1]);
// 只有当lnum>0时才需要计算,下面同理
if(lnum){
tmp = sum[i-1] * (v[i]-1) - pre[i-1];
tmp += lnum;
}
int rnum = min(need-lnum, sum[n] - sum[i]);
if(rnum){
tmp += (pre[n] - pre[i]) - (sum[n]-sum[i]) * (v[i]+1);
tmp += rnum;
}
res = min(res, tmp);
}
// 先右后左
{
ll tmp = 0;
int rnum = min(need, sum[n] - sum[i]);
if(rnum){
tmp = (pre[n] - pre[i]) - (sum[n] - sum[i]) * (v[i]+1);
tmp += rnum;
}
int lnum = min(need-rnum, sum[i-1]);
if(lnum){
tmp += sum[i-1] * (v[i]-1) - pre[i-1];
tmp += lnum;
}
res = min(res, tmp);
}
}
cout << res << endl;
return 0;
}
2400难度的题目,平常心看待就好了
注:转载请注明出处