CF1244E Minimizing Difference

CF1244E Minimizing Difference - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

首先容易想到把 \(a\) 升序排列。

想让 \(\max - \min\) 最小,一定是选择降低 \(\max\),提高 \(\min\)

先考虑一个问题:单纯最大化 \(\min\)

想一下,提高 \(\min\) 其实是这样一个过程:一直加 \(a_1\),直到 \(a_1 = a_2\),然后一直同步加 \(a_1, a_2\),直到 \(a_1=a_2=a_3\),再一直同步加 \(a_1,a_2,a_3\)……直到最后 \(k\) 被用尽。易证不这么加就是浪费操作。

假设我们刚刚同步把 \(a_1 \sim a_{i - 1}\) 加到和 \(a_i\) 齐平,现在我们要把 \(a_1 \sim a_i\) 加到和 \(a_{i +1}\) 齐平。

如果代价足够用,我们就考虑把 \(a_1 \sim a_i\) 花费 \(i(a_i - a_{i-1})\) 的代价一直加到 \(a_{i + 1}\),然后 \(i \gets i + 1\)

如果代价不够用了,就考虑把 \(a_1 \sim a_i\) 一直加到代价耗尽,答案就是此时能加到的位置。

单纯降低 \(\max\) 是同理的。现在我们综合考虑。

如果我们刚刚把 \(a_1\) 加到和 \(a_2\) 齐平,这时有两条路走:

  • \(a_1, a_2\) 一直加到和 \(a_3\) 齐平;
  • \(a_n\) 一直减到和 \(a_{n-1}\) 齐平。

那么我们一定选择第二条路。因为第一条路每花费 \(2\) 的代价才能提高 \(1\)\(\min\),但第二条路每花费 \(1\) 的代价就能降低 \(1\)\(\max\)。第二条路走完(齐平了)如果还有操作剩余,再走第一条路。

因此最优解一定是重复执行以下三步得到,初始时 \(l = 1\)

  • 尝试把 \(a\) 开头长度为 \(l\) 的前缀加到和后面那个数齐平,并把 \(a\) 结尾长度为 \(l\) 的后缀减到和前面那个数齐平;
  • 如果齐平成功,\(l \gets l +1\)
  • 如果齐平不成功,说明步数要不够了。尽可能让剩下的步数给前缀的 \(l\) 个数 \(+1\),后缀的 \(l\) 个数 \(-1\),可以让 \(\max\)\(\min\) 再靠近【剩下的步数除以 \(l\),下取整】个单位。此时得到最终答案。

考虑前缀和后缀的汇合点。这里我们让上述算法在处理 \(l= \lfloor \dfrac{n - 1}{2} \rfloor\) 后终止。那么在 \(n\) 为奇数时刚刚好,\(n\) 为偶数时还有中间两个点没齐平,手动尝试齐平一下即可。

时间复杂度 \(\mathcal{O}(n \log n)\)

/*
 * @Author: crab-in-the-northeast 
 * @Date: 2022-10-18 09:56:47 
 * @Last Modified by: crab-in-the-northeast
 * @Last Modified time: 2022-10-18 10:45:39
 */
#include <bits/stdc++.h>
#define int long long
inline int read() {
    int x = 0;
    bool flag = true;
    char ch = getchar();
    while (!isdigit(ch)) {
        if (ch == '-')
            flag = false;
        ch = getchar();
    }
    while (isdigit(ch)) {
        x = (x << 1) + (x << 3) + ch - '0';
        ch = getchar();
    }
    if(flag)
        return x;
    return ~(x - 1);
}

const int maxn = (int)1e5 + 5;
int a[maxn];

inline int min(int a, int b) {
    return a < b ? a : b;
}

signed main() {
    int n = read(), k = read();
    for (int i = 1; i <= n; ++i)
        a[i] = read();
    
    std :: sort(a + 1, a + 1 + n);
    int ans = a[n] - a[1];

    for (int l = 1, r = n; l <= ((n - 1) >> 1); ++l, --r) {
        int t = (a[l + 1] - a[l] + a[r] - a[r - 1]) * l;
        if (k < t) {
            printf("%lld\n", ans - k / l);
            return 0;
        } else {
            ans = a[r - 1] - a[l + 1];
            // 可以写成 ans -= t / l;
            k -= t;
        }
    }

    if ((n & 1) == 0) {
        int l = (n >> 1);
        ans -= min(a[l + 1] - a[l], k / l);
    }

    printf("%lld\n", ans);
    return 0;
}
posted @ 2022-10-18 10:49  dbxxx  阅读(36)  评论(0编辑  收藏  举报