【ybtoj高效进阶 21261】头文字 C(单调队列优化DP)

这篇博客介绍了一种利用单调队列优化动态规划的方法来解决数组分割问题。具体而言,给定一个数组,目标是将其分成若干段,每段的元素和保持非增。首先将数组翻转并进行单调不降排列,然后通过动态规划构建一个O(n^2)复杂度的解决方案,维护一个单调递增的队列来优化转移过程。最终代码实现了这一算法,并在O(n^2)的时间内求解出最多可以分成的段数。
摘要由CSDN通过智能技术生成

头文字 C

题目链接:ybtoj高效进阶 21261

题目大意

给你一个数组,然后问你最多能分成多少段,使得每一段的值不增。
每一段的值是这一段的数的和。

思路

首先我们把序列翻转,变成要单调不降。

然后考虑 DP,设 f i , j f_{i,j} fi,j 为把前 i i i 个数最多能分成多少段(最后一段是 j + 1 ∼ i j+1\sim i j+1i)。
那不难得到一个 O ( n 3 ) O(n^3) O(n3) 的转移: f i , j = ∑ k < j , S j − S k ⩽ S i − S j { f j , k + 1 } f_{i,j}=\sum\limits_{k<j,S_j-S_k\leqslant S_i-S_j}\{f_{j,k}+1\} fi,j=k<j,SjSkSiSj{fj,k+1}

然后我们考虑优化,首先我们要想到一个性质,就是在上面的转移中同样的 i , j i,j i,j,如果两个 k k k 都可以转移,而且 k 1 < k 2 k_1<k_2 k1<k2,那么 k 2 k_2 k2 一定不会比 k 1 k_1 k1 劣。
(因为你都可以转移了,我们肯定就是要缩小上一段的,让它恰好比这一段大一点,所以 k k k 能靠后就靠后)

那我们就可以进行一个优化,直接设 f i f_{i} fi 为搞定前 i i i 个数最多分成多少段。
然后用另一个数组 s u f i suf_i sufi 记着最后一段的大小。
那我们就得到 O ( n 2 ) O(n^2) O(n2) 的转移:
f i = ∑ j < i , s u f j ⩽ S i − S j { f j + 1 } f_{i}=\sum\limits_{j<i,suf_j\leqslant S_i-S_j}\{f_{j}+1\} fi=j<i,sufjSiSj{fj+1}

稍微把条件移项一下有 S i ⩾ s u f j + S j S_i\geqslant suf_j+S_j Sisufj+Sj,发现 S i S_i Si 单调递增,决策的集合越来越大。
我们就维护一个 j j j 递增, s u f j + S j suf_j+S_j sufj+Sj 递增的单调队列即可。

代码

#include<cstdio>
#define INF 0x3f3f3f3f3f3f3f3f

using namespace std;

int n, a[100002];
int ans, suf[100002];
int f[100001], sta[100001];

int main() {
//	freopen("read.txt", "r", stdin);
//	freopen("block.in", "r", stdin);
//	freopen("block.out", "w", stdout);
	
	scanf("%d", &n);
	for (int i = 1; i <= n; i++) scanf("%d", &a[n - i + 1]);
	
	for (int i = 1; i <= n; i++)
		a[i] += a[i - 1];
	
	int l = 1;
	sta[++sta[0]] = 0;
	for (int i = 1; i <= n; i++) {
		while (l < sta[0] && suf[sta[l + 1]] + a[sta[l + 1]] <= a[i]) l++;//找到最右的能满足的
		f[i] = f[sta[l]] + 1;//转移
		suf[i] = a[i] - a[sta[l]];//放进单调队列里面
		while (l <= sta[0] && suf[i] + a[i] <= suf[sta[sta[0]]] + a[sta[sta[0]]])
			sta[0]--;
		sta[++sta[0]] = i;
	}
	
	printf("%d", f[n]);
	
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值