裁剪序列题解

裁剪序列

## 题目

给定一个长度为 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;

}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值