cf973div2 DMinimize the Difference

https://mirror.codeforces.com/contest/2013/problem/D

写法很多,依次讲述,二分,前缀和。

二分

第一维二分差值,第二维二分max和min,检测就是一个反悔贪心,让所有数都在l,r的范围内。

因为差值确定此处min就是max - 差值

反悔贪心的思路就是,数字大于r时,到r的差是必须转移到下一位的,r到l的差是可以反悔的。数字大于l小于r时,数字到l的差是可以反悔的。数字小于l时,是要填充的。

当然还有一种二分max和min的方法,原理大差不差。

ll ck(ll low, ll hig) {
    for (ll i = 0; i < n; i++) a[i] = b[i];
    ll more = 0;
    for (int i = 0; i < n - 1; i++) {
        if (a[i] > hig) {
            a[i + 1] += a[i] - hig;
            a[i] = hig;
            more += hig - low;
        } else if (a[i] < low) {
            if (more < low - a[i]) return 1;
            more -= low - a[i];
            a[i] = low;
        } else {
            more += a[i] - low;
        }
    }
    if (a[n - 1] > hig) return 0;
    if (more < low - a[n - 1]) return 1;
    return 2;
}
bool check(ll k) {
    ll l = 0, r = 1e12;
    while (l <= r) {
        ll mid = l + (r - l) / 2;
        if (mid - k < 0) {
            l = mid + 1;
            continue;
        }
        ll ans = ck(mid - k, mid);
        if (ans == 1) {
            r = mid - 1;
        } else if (ans == 0) {
            l = mid + 1;
        } else {
            return 1;
        }
    }
    return 0;
}
void solve() {
    cin >> n;
    for (ll i = 0; i < n; i++) {
        cin >> a[i];
        b[i] = a[i];
    }
    ll l = 0, r = 1e12;
    while (l <= r) {
        ll mid = l + (r - l) / 2;
        if (check(mid)) {
            r = mid - 1;
        } else {
            l = mid + 1;
        }
    }
    cout << l << endl;
}

前缀和

基于一些结论,本题对数组的操作是左减小右增大。

从左往右遍历,把目前的数字分成i份,一定是最大的,因为操作会让左减小。

从右往左遍历,把目前的数字分成i份,一定是最小的,因为操作会让右增大。

然后在遍历的过程中我们分别得出,满足最大性质的最小值,以及满足最小性质的 最大值,两者作差就是答案。

补充最大值为什么向上取整,其实这和向下取整是差不多的。向下取整代表着,找到可能的最小值,向上就是可能的最大值。手写两个就明白了。

void solve() {
    cin >> n;
    for (ll i = 1; i <= n; i++) {
        cin >> a[i];
    }
    ll sum = 0;
    ll maxm = 0, minm = LLONG_MAX;
    for (int i = 1; i <= n; i++) {
        sum += a[i];
        minm = min((sum / i), minm);
    }
    reverse(a + 1, a + 1 + n);
    sum = 0;
    for (int i = 1; i <= n; i++) {
        sum += a[i];
        maxm = max((sum + i - 1) / i, maxm);
    }
    cout << maxm - minm << endl;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值