AcWing 299. 裁剪序列

题目链接:裁剪序列

知识点:

  • 动态规划,单调队列,multiset,贪心,双指针
分析

首先使用闫氏DP分析法,

状态表示:

  • 集合:f[i]表示所有前i个数的合法划分方案的集合。
  • 属性:最小值。

状态计算:

  • 根据最后一段的长度来分类。
  • 可以为分为1, 2, ..., k, ..., i类,其中第k类为f[i - k] + a_maxa_maxa[k, i]区间的最大值。

如果区间[j, k]的最大值为a_max,那么该段的最小值为f[j - 1] + a_max即取左端点,因为f[]是单调递增的(贪心证明)。

用双指针维护一个最远的j,使得sum[j,i] <= M,再在[j,i]区间里用单调队列维护一个降序的区间最大值,用multiset作为一个支持查询最小值和删除元素的容器。

代码
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
// #include 
// #include 

using namespace std;

typedef long long LL;

const int N = 1e5 + 10;

int n;
LL m;
int w[N], q[N];
LL f[N];
multiset<LL> S;  // 平衡树

void remove(LL x) {
	auto it = S.find(x);
	S.erase(it);
}

void solve() {
	cin >> n >> m;
	
	for (int i = 1; i <= n; i ++ ) {
		cin >> w[i];
		if (w[i] > m) {  // 没有解
			puts("-1");
			return;
		}
	}
	
	LL sum = 0;  // [j,i]区间和
	int hh = 0, tt = -1;  // 单调队列的头尾指针
	for (int i = 1, j = 1; i <= n; i ++ ) {
		sum += w[i];  // 区间和首先加上当前元素
		while (sum > m) {  // 超过范围,j指针需要向前移动
			sum -= w[j ++ ];
			if (hh <= tt && q[hh] < j) {  // 单调队列队头不在窗口内
				// S中不能维护队头的最值,所以最少要两个元素
				if (hh < tt) remove(f[q[hh]] + w[q[hh + 1]]);
				hh ++ ;
			}
		}
		
		while (hh <= tt && w[q[tt]] <= w[i]) {  // 更新队尾
			if (hh < tt) remove(f[q[tt - 1]] + w[q[tt]]);  // 同上面
			tt -- ;
		}
		q[ ++ tt] = i;
		if (hh < tt) S.insert(f[q[tt - 1]] + w[q[tt]]);  // 如果两个元素,需要插入到S中
		
		f[i] = f[j - 1] + w[q[hh]];  // 队头元素的值
		if (S.size() > 0) f[i] = min(f[i], *S.begin());  // 队头以外的最小值
	}
	
	printf("%lld\n", f[n]);
}

int main() {
	ios::sync_with_stdio(0), cin.tie(0);
	
	solve();
	
	return 0;
}

你可能感兴趣的:(动态规划,算法,c++)