Cut the Sequence(单调队列DP+set)

题面

image

  • 大意:一段长度为n的序列,分成若干段,每段值的总和不能超过m,求各段中最大值加起来的最小值。

其实最朴素的DP还是很好想的,以f[i]表示i及i以前已经分好所需的最小值,a[i]表示i点的值,那么枚举k,k满足

  1. k<i .
  2. \[(\sum^{j <= i }_{j = k+1}a[j] )<= m. \]

可用k更新f[i] : $$f[i]=\min(f[k]+\max(a[k+1->i]))$$
时间复杂度为O(n^2 ),明显T了。
那么一定是有很多的冗杂运算了,下面我们就来找出并避免这些运算。

  • 首先\(f[i]一定是单调不降的\),这很显然。
    那么我们可以找出来一些规律,当 \(\max(a[k+1->i])\) 固定时,\(k越小越好\),当\(\max(a[k+1->i])\)改变时,无法直接判断,仍是一种有可能成为最小值的情况,那么其他的k是没有用的,即 $$j1<j2,a[j1]<=a[j2]时,j2无用$$
    到这里已经可以看出来需要\(维护一个单调递减的单调队列\).
    但是单调队列中是a[k]递减,但\({f[k]+\max(a[k+1->i])}\)并不单调,需要用multiset来维护一下,其中\(\max(a[k+1->i])\)其实就是队列中的下一个元素的值,insert(f[k]+a[k的下一个元素])即可,队列与set同时insert和pop(这里的细节较多,后面看代码注释),因为每个k最多入队和出队一次,单调队列均摊O(1),set维护log(N),总时间复杂度O(Nlog(N)).
    看代码:注意数据范围,得开long long。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ld;
typedef pair<int,int> pii;
#define mk make_pair
#define ps push_back
#define fi first
#define se second
inline int read(){
	char c=getchar();int x=0,f=1;
	while(c<'0'||c>'9')f=c=='-'?-1:1,c=getchar();
	while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
	return f*x;
}
const int N=1e5+10,inf=0x7fffffff;
long long n,m,f[N],a[N];
int main()
{
	#ifndef ONLINE_JUDGE
	freopen("in.in","r",stdin);
	freopen("out.out","w",stdout);
	#endif
	n=read(),m=read();
	ll x=0,k=1,tp=0;
	for(int i=1;i<=n;++i){
		a[i]=read();if(a[i]>m)return puts("-1"),0;//不可能情况
	}
	multiset<ll> s;
	deque<ll> q;
	for(int i=1;i<=n;++i){
		x+=a[i];
		while(x>m)x-=a[k++];//保证sum(a[k->i])<=m
		while(!q.empty()&&q.front()<k){//在k之前的点不要,pop掉
			tp=f[q.front()];q.pop_front();
			if(!q.empty())s.erase(tp+a[q.front()]);//当!q.empty()时,说明set里已经放入了\
			(a[q.front()]+f[q.front()的上一个元素]),需要pop(tp+a[q.front()]),下面一样。
		}
		while(!q.empty()&&a[q.back()]<=a[i]){//保证队列单调递减
			tp=a[q.back()];q.pop_back();
			if(!q.empty())s.erase(tp+f[q.back()]);
		}
		if(!q.empty())s.insert(f[q.back()]+a[i]);//队列中每两个相邻元素就得insert一次
		q.push_back(i);
		f[i]=f[k-1]+a[q.front()];//选择了k->i整个区间,\
		因为该段区间最大值(a[q.front()])与f[k-1]也构成一对可能解。
		if(s.size())f[i]=min(f[i],*s.begin());//注意判空
	}
	printf("%lld\n",f[n]);
	return 0;
}
posted @ 2024-06-08 07:16  lzrG23  阅读(12)  评论(0编辑  收藏  举报