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;
}