裁剪序列
## 题目
给定一个长度为 N 的序列 A,要求把该序列分成若干段,在满足“每段中所有数的和”不超过 M 的前提下,让“每段中所有数的最大值”之和最小。
试计算这个最小值。
#### 输入格式
第一行包含两个整数 N 和 M。
第二行包含 N 个整数,表示完整的序列 A。
#### 输出格式
输出一个整数,表示结果。
如果结果不存在,则输出 −1。
#### 数据范围
$0≤N≤105$$0≤M≤1011$序列A中的数非负,且不超过$10^6$
## 思路
我们采用dp来解决这道题目,根据数据规模可知.一定是一维状态,不妨设`dp[i]` 表示前i个元素最大值之和的最小值
可以很容易想到`dp[i] = max(dp[k] + maxElement{k + 1 , i})` 其中k的取值范围是从i开始向前取得极限长度
如果采用这个公式来进行状态转移, 时间复杂度来到O($n^2$)是我们不能接受的,所以需要做优化
仔细观察题目,我们发现$dp[i]$ 是**单调不减**的,$maxElement(k+1,i)$ 当i固定时**单调不增**的. 证明如下: 若$i > m > k$
则区间$(m + 1 , i)$包含在区间$(k + 1 , i)$ 中,所以区间最大值必然小于等于$(k + 1 , i)$
基于这两个性质,我们可以推导出一些关系: 假设$m$是区间$(k + 1 , i)$的最大值:则有 : $maxElement(k + 1 , i) == maxElement(m + 1 , i)$
又因为$dp[i]$ **单调不减**可得$dp[k] + maxElement(k + 1 , i) <= dp[m] + maxElement(m + 1 , i)$
因此在进行状态转移的时候我们只需要考虑k这个的点而不需要考虑m这个点
那么k到底是什么点 , 根据推到我们可以得出是$maxElement(k + 1 , i) == maxElment(m + 1 , i)$ 的最小点,即最近的比$m$大的点后一个点. 我们可以采用一个单调队列去维护这些点, 队列中从队头到队尾单调递减,然后使用一个可修改元素的堆去获取这些值的最小值
#include <iostream>
#include <set>
#include <vector>
#include <unordered_set>
#include <queue>
#include <climits>
#include <stack>
using namespace std;
int n;
long long m;
int main(){
// 定义dp[i]为前i长度的结果
// 易证 dp[i]是单调不减的 . 定义 m(i , j) 为区间i到j的最大值 , 对于固定的j m(i,j)是单调不增的
// 设k为 m(i , j)中最值 则 m(i , j) == m (k , j);
// 由于dp[i]是单调不减的 , 因此 dp[k - 1] + m(k , j) >= dp[i] + m(i , j)
// dp[i] = dp[j - 1] + m(j , i)
// 若我们假设m(j , i) == nums[j]
cin >> n >> m;
vector<int> data(n);
vector<int> dp(n);
for (int i = 0; i < n; i++) {
cin >> data[i];
if (data[i] > m) {
cout << -1;
return;
}
}
// 预处理每个数前面最近更大值
// 单调栈
vector<int> near(n);
stack<int> s;
for (int i = 0; i < n; i++) {
while (!s.empty() && data[s.top()] <= data[i]) {
s.pop();
}
if (s.empty()) {
near[i] = -1;
}
else {
near[i] = s.top();
}
s.emplace(i);
}
// 维护一个单调队列和一个multimap
long long sum = 0;
int startPos = 0;
multiset<int> opt;
deque <pair<int,int>> q; // 规定第一个是索引,第二个是当前的最优解,队列中的元素在data中的值单调递减
// 每次只需要更新队列最前面的元素
for (int i = 0; i < n; i++) {
// 如果队头过期就出队
sum += (long long)data[i];
while (sum > m) {
sum -= (long long)data[startPos++];
}
while (!q.empty() && q.front().first < startPos) {
opt.erase(opt.find(q.front().second));
q.pop_front(); // 出队
}
// 从队尾出队不单调的元素
while (!q.empty() && data[q.back().first] <= data[i])
{
opt.erase(opt.find(q.back().second));
q.pop_back();
}
// 计算当前的权值
int wight = 0;
if (max(startPos - 1, near[i]) == -1)
wight = data[i];
else
wight = dp[max(startPos - 1, near[i])] + data[i];
// 插入当前的权值
q.push_back({ i , wight });
opt.emplace(q.back().second);
// 更新队头
opt.erase(opt.find(q.front().second));
if (max(startPos - 1, near[q.front().first]) == -1)
wight = data[q.front().first];
else
wight = dp[max(startPos - 1, near[q.front().first])] + data[q.front().first];
q.front().second = wight;
opt.emplace(wight);
dp[i] = *opt.begin();
}
cout << dp[n - 1] << endl;
return 0;
}