【ICPC】2022济南站 A. Tower | 贪心

830 阅读2分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第11天,点击查看活动详情

【ICPC】2022济南站 A. Tower | 贪心

题目链接

Problem - A - Codeforces

题目

image.png

题目大意

给定具有 nn 个元素的数组 a1,a2,a3,...,ana_1,a_2,a_3,...,a_n。首先从 数组中删除恰好 mm 种不同的元素,然后进行若干次一下三种操作之一:

  • 从数组中选择一个没有被删除的元素,将其的值减小 11
  • 从数组中选择一个没有被删除的元素,将其的值增加 11
  • 从数组中选择一个没有被删除的元素,将其的值除以 22 下取整。

问最少进行多少次操作可以使得整个数组中剩下的 nmn-m 个元素完全相同。且操作过程中应保证数组中任一元素不小于 11

思路

如果我们只能进行加减操作,最小的操作次数一定可以在把所有数都变成中位数中取得。所以当我们加上除以 22 操作后,每个数都不断除以 22 直到其变成 11,过程中经历的值就是最终我们可能把所有的数变成的值。有不超过 30n30n 个。

如果我们想把数 aia_i 变成 xx,先加减再除以 22 的或者三种操作交替进行,操作次数一定不小于先执行若干次除以 22 操作后再加减。所以求把数 aia_i 变成 xx,的步数就是

minj=0log2(ai)(x(ai>>j)+j)\min_{j=0}^{\lfloor \log_2(a_i)\rfloor}(|x-(a_i>>j)|+j)

对于一个可能的终值,我们可以求解把原数组中的每个数向终值靠拢需要的最小步数,然后对最小步数进行排序取前 nmn-m 小的求和。枚举所有可能的终值分别求解取最小值就是最终的答案。

这样写是 O(n2log2n)O(n^2log^2n) 的,会 TLE,容易发现我们第一部分求出的可能的终值有很多重复,去重即可 AC。

代码

#include <stdio.h>
#include <algorithm>
using namespace std;
int T,n,m;
using LL=long long;
const int N=501;
LL a[N],d[31*N],c[N];
int mid[N*30+5];
LL solve()
{
	LL ans=1e18,sum,cnt,z=0;
	scanf("%d%d",&n,&m);
	int tot=1;
	for (int i=1;i<=n;++i)
	{
		scanf("%lld",&a[i]);
		for (int j=a[i];j;j/=2) mid[++z]=j;
	}
	sort(mid+1,mid+1+z);
	for (int i=2;i<=z;++i)
		if (mid[i]!=mid[i-1]) mid[++tot]=mid[i];
	for (int t=1;t<=tot;++t)
	{
		sum=0;
		for (int i=1;i<=n;++i)
		{
			c[i]=1e18;
			cnt=0;
			for (int j=a[i];j;j/=2,cnt++)
				c[i]=min(c[i],cnt+abs(j-mid[t]));
		}
		sort(c+1,c+1+n);
		for (int i=1;i<=n-m;++i) sum+=c[i];
		ans=min(ans,sum);
	}
	return ans;
}
int main()
{
	scanf("%d",&T);
	while (T--)
		printf("%lld\n",solve());
        return 0;
}