题解【CF1328F Make k Equal】

\[\texttt{Description} \]

给一个 \(n\)\(k\) ,以及长度为 \(n\) 的序列 \(a\)

有两种操作:

  • 让数组中其中一个最小值的值加一。
  • 让数组中其中一个最大值的值减一。

问:最少几次操作,可以使得数组中至少有 \(k\) 个数相等。

\[\texttt{Solution} \]

  • 首先有一个非常非常非常显然的结论:

定存在最优解,满足最后相等的 \(k\) 个数的值,为 \(a\) 中出现过的值。

  • 为了方便做题,我们将序列 \(a\) 打包成若干个二元组 \((num,cnt)\) ,表示值为 \(num\) 的数在 \(a\) 中有 \(cnt\) 个。

  • 此时我们注意到:对于任意数 \(x\) ,若想通过【最小数加一】这个操作使得小于 \(x\) 的数变成 \(x\) ,必须要使得所有小于 \(x\) 的所有数先变成 \(x-1\) ,再进行一次该操作,才能使得一个小于 \(x\) 的数变成 \(x\)

  • 【最大数减一】同理。

    扫描二维码关注公众号,回复: 10209501 查看本文章
  • 于是,我们处理出 " 将小于 \(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)}\)评测链接

\[\texttt{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\n",ans);

	return 0;
}

\[\texttt{Thanks} \ \texttt{for} \ \texttt{watching} \]

猜你喜欢

转载自www.cnblogs.com/cjtcalc/p/12579209.html