zoukankan      html  css  js  c++  java
  • 题解【CF1328F Make k Equal】

    [ 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]) ,有三种情况:

    1. 用【最小数加一】和【最大数减一】两个操作使得有 (k)(num[i]) 相等,此时最少步数就是 (pre[i]+suf[i]+res)
    2. 用【最小数加一】操作使得有 (k)(num[i]) 相等,若 (pcnt[i]geq res) ,此时最少步数就是 (pre[i]+res)
    3. 用【最大数减一】操作使得有 (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} ]

  • 相关阅读:
    配置双jdk
    检测一个页面所用的时间的js
    java发送短信开发,第三方接口方法
    jq的常用事件及其案例
    ajax无法返回视图
    SpringMVC IO 文件上传
    及上一篇linux安装mysql的说明
    centos6.10下安装mysql8.0.16root密码修改的坑
    线程池学习
    数组的分隔
  • 原文地址:https://www.cnblogs.com/cjtcalc/p/12579209.html
Copyright © 2011-2022 走看看