Blocking Elements
題目描述
給定一個長度為 \(n\) 的序列 \(A\),你需要劃分這個序列。先任意選擇若干個位置,假定你選擇了 \(m\) 個位置,這些位置分別為 \(B_1,B_2...B_m\) ,這一次劃分的代價為下面兩個量中的最大值:
- \(\sum\limits_{i=1}^{m}A_{B_{i}}\) .
- \(\max\limits_{i=0}^{m}{\large{\sum\limits_{j=B_i+1}^{B_{i+1}-1}}{A_j}}\) 。 特別的,定義\(B_0=0,B_{m+1}=n+1\),同時,若 \(B_i=B_i+1\) ,則在原式中 \(\sum\) 一項的值你應當視為 0。
即為:你選擇了若干位置,這些位置將原序列分隔成了若干段。代價是你選擇的這些位置的元素和與每一段中所有的元素和的最大值。
給定 \(n\) 與序列 \(A\) ,求最小分隔代價。多測,\(\sum n \le 1e5\) .
思路
題目中大概的意思抽象成“最大值的最小值”,我們會想到二分答案。
則設分割後的到的代價不大於 \(val\),則要滿足一下兩個條件:
- 選擇的斷點的元素總和要小於等於 \(val\)。(條件1)
- 相鄰的兩個斷點之間的元素和的最大值要小於等於 \(val\)。(條件2)
我們設 \(dp_i\) 為 \(i\) 位置前,滿足條件2的劃分方案中選擇斷點的元素總和的最小值。
列出 \(dp\) 方程轉移式:
\[dp_i = a_i + \min_{pre_{i - 1} - pre_k <= val}\{dp_k\}
\]
而我們發現後面的這個式子是可以用單調列隊最佳化成 \(O(n)\) 複雜度的。
最後返回dp[n + 1] <= val
即可。
(我們可以新增一個a[n + 1] = 0
的點來處理邊界問題)。
\(code\)
#include<iostream>
#include<algorithm>
#include<queue>
#include<deque>
using namespace std;
#define int long long
const int MAXN = 1e5 + 7;
int n,dp[MAXN];
int a[MAXN],pre[MAXN];
int l,r;
deque<int> q;
bool check(int val){
for(int i = 0;i <= n + 1;i++) dp[i] = 0;
q.clear(),q.push_back(0);
for(int i = 1;i <= n + 1;i++){
while(!q.empty() && pre[i - 1] - pre[q.front()] > val){
q.pop_front();
}
dp[i] = a[i] + dp[q.front()];
while(!q.empty() && dp[q.back()] >= dp[i]) q.pop_back();
q.push_back(i);
}
return dp[n + 1] <= val;
}
signed main(){
int _;
cin>>_;
while(_--){
cin>>n;
a[n + 1] = 0;
for(int i = 1;i <= n;i++) cin>>a[i],pre[i] = pre[i - 1] + a[i];
l = 1,r = pre[n];
while(l < r){
int mid = (l + r) / 2;
if(check(mid)) r = mid;
else l = mid + 1;
}
cout<<l<<endl;
}
return 0;
}