Codeforces Round #629 (Div. 3) F. Make k Equal(思维/前后缀处理)

题目链接


在这里插入图片描述
1.参考了此dl的博客,首先证明选择的数一定在序列内:现在有两个数 a , b ( a < b ) a,b(a<b) a,ba<b,a的数量是 x x x个,b的数量是 y y y个,现在在 a − b a-b ab之间选取一个数 c c c,假定最后答案就是 c c c,那么花费为 ( c − a ) ∗ x + ( b − c ) ∗ y (c-a)*x + (b-c)*y (ca)x+(bc)y,选取 c = a c=a c=a或者 c = b c=b c=b时花费为 ( b − a ) ∗ m i n ( x , y ) (b-a)*min(x,y) (ba)min(x,y),显然 ( b − a ) ∗ m i n ( x , y ) ≤ ( c − a ) ∗ x + ( b − c ) ∗ y (b-a)*min(x,y) \leq (c-a)*x + (b-c)*y (ba)min(x,y)(ca)x+(bc)y。那么最终答案一定是原数组存在的数

2.那么显然就是考虑序列的每一个数,显然需要构造是数量是 k k k减去这个数在序列出现的次数,那么我们排序后处理一下位置即可。首先比当前 a [ i ] a[i] a[i]小的要想达到 a [ i ] a[i] a[i],首先必须都达到 a [ i ] − 1 a[i]-1 a[i]1,因为每次只能操纵最小值;而右半部分想要达到 a [ i ] a[i] a[i],首先必须都达到 a [ i ] + 1 a[i]+1 a[i]+1,因为每次只能操纵最大值。那么左边的贡献是左边数量乘以 a [ i ] − 1 a[i]-1 a[i]1减去前缀和,右边同理区间和减去右边的数量乘以a[i]+1,这样提前处理前缀和即可。最后是分成三种情况

  • 只要左边就能构成 k k k个数
  • 只要右边就能构成 k k k个数
  • 两边都需要

3.时间复杂度显然仅 O ( n ) O(n) O(n)

#include <set>
#include <map>
#include <stack>
#include <queue>
#include <math.h>
#include <cstdio>
#include <string>
#include <cstring>
#include <sstream>
#include <iostream>
#include <algorithm>
#include <unordered_map>
using namespace std;
#define lowbit(x) (x&(-x))
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> P;
const double eps=1e-8;
const double pi=acos(-1.0);
const int inf=0x3f3f3f3f;
const ll INF=1e18;
const int Mod=1e9+7;
const int maxn=2e5+10;

int a[maxn],L[maxn],R[maxn];
ll sum[maxn];

int main(){
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    int n,k;
    cin>>n>>k;
    memset(sum,0,sizeof sum);
    memset(L,0,sizeof L);
    memset(R,0,sizeof R);
    for(int i=1;i<=n;i++){
        cin>>a[i];
    }
    sort(a+1,a+1+n);
    for(int i=1;i<=n;i++){
        L[i]=(a[i]==a[i-1]?L[i-1]+1:1);  //记录每个位置左有连续几个这样的数
        sum[i]=sum[i-1]+a[i];  //记录前缀和
    }
    for(int i=n;i>=0;i--){
        R[i]=(a[i]==a[i+1]?R[i+1]+1:1);  //记录每个位置右有连续几个这样的数
    }
    ll ans=INF;
    for(int i=1;i<=n;i++){
        int l=i-1,r=i+1,now=0,cnt=k-1,gl=a[i]-1,gr=a[i]+1; //l和r记录位置;cnt为k-1因为除a[i]本身外还需构造出k-1个数;gl是左半部分需要达到的大小,gr是右半部分需要达到的大小
        int p=l,q=r;
        if(a[l]==a[i]) now+=L[l],l-=L[p];
        if(a[r]==a[i]) now+=R[r],r+=R[q];
        if(now>=cnt){  //当前a[i]的个数可以达到了k,无需构造
            ans=0;
            break;
        }
        cnt-=now;  //还需构造几个a[i]
        int numl=l,numr=n-r+1;  //左半部分和右半部分分别需要的构造数
        ll cl=1LL*gl*numl-sum[l];  //左贡献
        ll cr=sum[n]-sum[r-1]-1LL*gr*numr;  //右贡献
        if(numl>=cnt) ans=min(cl+cnt,ans);  //左边能独立完成多余k-1个数为一种情况,同理右边
        if(numr>=cnt) ans=min(cr+cnt,ans);
        ans=min(cl+cr+cnt,ans);  //两边都需要
    }
    cout<<ans<<endl;
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值