Codeforces Round #922 (Div. 2) ABCD
D. Blocking Elements
题意:在 \(a\) 中取 \(m\) 个元素,将原数组划分为 \(m + 1\) 个子段(可以为空),问 \(m\) 个元素和与子段和中的最大值最小为多少。
二分答案。
考虑 \(f[i]\) 表示取第 \(i\) 个元素,且在前 \(i\) 个中取出元素和的最小值。
\[f[i] = a[i] + min(f[j]) \ \ \ \ (sum[i - 1] - sum[j] \le mid)
\]
\(sum\) 为前缀和。
显然可以用一个单调队列 \(O(n)\) 去维护。
#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i <= (b); ++ i)
#define per(i, a, b) for(int i = (a); i >= (b); -- i)
#define pb emplace_back
#define All(X) X.begin(), X.end()
using namespace std;
using ll = long long;
constexpr int N = 1e5 + 5;
ll n, a[N], f[N];
int q[N], hh, tt;
bool check(ll v) {
ll ans = 1e15;
q[0] = 0;
hh = tt = 0;
rep(i, 1, n) {
while(hh <= tt && a[i - 1] - a[q[hh]] > v) ++ hh;
f[i] = a[i] - a[i - 1] + f[q[hh]];
if(a[n] - a[i] <= v) ans = min(ans, f[i]);
while(hh <= tt && f[i] <= f[q[tt]]) -- tt;
q[++ tt] = i;
}
return ans <= v;
}
void solve() {
cin >> n;
rep(i, 1, n) cin >> a[i], a[i] += a[i - 1];
ll l = 0, r = 1e15;
while(l < r) {
ll mid = l + r >> 1;
if(check(mid)) r = mid;
else l = mid + 1;
}
cout << l << '\n';
}
int main() {
ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
int T = 1;
cin >> T;
while(T --) solve();
return 0;
}