开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第11天,点击查看活动详情
【ICPC】2022济南站 A. Tower | 贪心
题目链接
题目
题目大意
给定具有 个元素的数组 。首先从 数组中删除恰好 种不同的元素,然后进行若干次一下三种操作之一:
- 从数组中选择一个没有被删除的元素,将其的值减小 。
- 从数组中选择一个没有被删除的元素,将其的值增加 。
- 从数组中选择一个没有被删除的元素,将其的值除以 下取整。
问最少进行多少次操作可以使得整个数组中剩下的 个元素完全相同。且操作过程中应保证数组中任一元素不小于 。
思路
如果我们只能进行加减操作,最小的操作次数一定可以在把所有数都变成中位数中取得。所以当我们加上除以 操作后,每个数都不断除以 直到其变成 ,过程中经历的值就是最终我们可能把所有的数变成的值。有不超过 个。
如果我们想把数 变成 ,先加减再除以 的或者三种操作交替进行,操作次数一定不小于先执行若干次除以 操作后再加减。所以求把数 变成 ,的步数就是
对于一个可能的终值,我们可以求解把原数组中的每个数向终值靠拢需要的最小步数,然后对最小步数进行排序取前 小的求和。枚举所有可能的终值分别求解取最小值就是最终的答案。
这样写是 的,会 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;
}