codeforces 657C - Bear and Contribution [想法题]

时间:2023-03-08 23:21:07
codeforces 657C - Bear and Contribution [想法题]

题目链接: http://codeforces.com/problemset/problem/657/C

--------------------------------------------------------------------------------------------------------

题目的特别之处在于只有 $+1$ $+5$ 这两种操作 我们要考虑如何利用这个条件

多想一下后可以发现 如果最优解的目标值为$x($将至少$k$个人的值增加到$x)$

那么一定存在一个人 他的初始值在 $[x - 4, x]$ 这个范围内

否则将$x$减去$5$后可以得到更优的解

因此可能成为最优解的目标值最多只有 $n * 5$ 种

现在考虑的便是在 $O(n)$ 枚举目标值的前提下 如何对于每个目标值快速计算出答案

我一开始的想法是根据 $mod 5$ 的余数分类 写$5$个数组记录下前缀和什么的

然而这样二分答案有一个 $log$ 二分数组下标又一个 $log$

尽管题目给了 $4s$ 应该可以过 但是总感觉这样做不够优雅

再次分析题目条件我们有可以发现 实际上每次询问都是与目标距离最小的 $k$ 个数 而这个$k$是不变的

于是现在问题就变成了维护一个数据结构 支持查找容器内最小的 $k$个数 以及添加一个数

这显然就是一个堆了 总的复杂度是 $O(n + nlogk)$

由于在 $mod 5$ 的$5$中情况下 所有初始值转移到目标值的代价多少的排序并不是一样的

因此我们维护$5$个堆就好

 #include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <queue>
using namespace std;
const int N = 2e5 + ;
const long long inf = 1e9;
int a[N], dis[N * ];
long long sum[];
long long ans = 1e18;
int n, k, b, c, cnt;
priority_queue <long long> q[];
int main()
{
scanf("%d%d%d%d", &n, &k, &b, &c);
b = min(b, c * );
for(int i = ; i <= n; ++i)
{
scanf("%d", &a[i]);
for(int j = ; j < ; ++j)
dis[cnt++] = a[i] + j;
}
sort(a + , a + + n);
sort(dis, dis + cnt);
cnt = unique(dis, dis + cnt) - dis;
int now = ;
for(int i = ; i < cnt; ++i)
{
int x = dis[i], kind =(inf + x) % ;
long long cost;
while(now <= n && a[now] <= x)
{
for(int j = ; j < ; ++j)
{
cost = (inf + j - a[now]) / * b + (inf + j - a[now]) % * c;
q[j].push(cost);
sum[j] += cost;
if((int)q[j].size() > k)
{
sum[j] -= q[j].top();
q[j].pop();
}
}
++now;
}
long long y = (inf + kind - x) / * b * k;
if((int)q[kind].size() == k)
ans = min(ans, sum[kind] - y);
}
printf("%lld\n", ans);
return ;
}