【题解】裁剪序列

【题解】【单调队列优化dp】裁剪序列

题目传送门

分析

首先考虑朴素的做法,设\(f_i\)表示把前i个数分成若干段,在满足每段中所有的数的和不超过M的前提下,各段的最大值之和最小是多少。容易写出状态转移方程:

\[f_i=\min\{f_j+\max\limits_{j+1 \leq k\leq i} \{a_k\}\},(0\leq j<i且\sum_{p=j+1}^i a_p<=M) \]

但这样转移是\(O(N^2)\)的,我们再思考有哪些决策j是我们“真正需要”的(可以成为最优决策)

观察我们的状态转移方程,发现决策的两个部分:
1.\(f_j\)是关于j的单调不下降函数
2.\(\max\limits_{j+1 \leq k\leq i} \{a_k\}\)是关于j的单调不上升函数

所以,如果\(\max\limits_{j+1 \leq k\leq i} \{a_k\}=\max\limits_{j \leq k\leq i} \{a_k\}\)并且\(\sum_{p=j}^i a_p<=M\)

决策j-1一定比决策j更优

那假设我们记录一个maxn表示j到i的最大值,我们从大到小枚举j,每次更新maxn,根据上面的结论,只有使maxn变大的j才有可能作为最优决策。特殊地,若j为最小的满足\(\sum_{p=j+1}^i a_p<=M\)的j,其也有可能更新答案

如下图,j=3使得maxn变大,因此j=3可以作为最优决策。j=1是最小的满足\(\sum_{p=j+1}^i a_p<=M\)的j,所以j=1也可以作为最优决策

于是我们思考如何维护这个决策集合,即i增加到i+1时,决策集合发生了哪些变化。假设原先的决策集合为\(S=\{j_1,j_2,\cdots,j_s\}\),不妨令\(j_1<j_2<\cdots<j_s\),显然\(a[j_1]>a[j_2]>\cdots>a[j_s]\)

那么当i变化后,我们模拟一下S的更新,先将\(i-1\)\(j_s\)进行比较,若\(a[i-1]>=a[j_s]\),把\(j_s\)扔出S,
然后,按照相同方式依次比较\(i-1\)\(j_{s-1},j_{s-2},j_{s-3}\cdots,\)直到集合为空或\(a[i-1]<a[j_{s-x}]\),然后将i-1加入集合

除此之外我们还要从\(j_1\)开始逐个检验\(\sum_{p=j_x+1}^i a_p\)是否小于等于\(M\),若否,则将其出队

是的,我已经情不自禁开始说出队了,因为这实际上就是一个单调队列呀

但是题目到这还没有做完,因为单调队列存的每个元素都是可能的最优决策,若不加以优化,仍要遍历整个队列导致复杂度爆炸

我们再来回顾下,我们原始的方程式:

\[f_i=\min\{f_j+\max\limits_{j+1 \leq k\leq i} \{a_k\}\},(0\leq j<i且\sum_{p=j+1}^i a_p<=M) \]

这个\(\max\limits_{j+1 \leq k\leq i} \{a_k\}\)好像很难处理,我们先想个简单的,如果min里面只有\(f_j\)的话怎么做?

既然是求最值,可以用一个二叉堆来维护,让它和单调队列建立一个映射,两个数据结构同时插入同时删除,至于处理\(\max\limits_{j+1 \leq k\leq i} \{a_k\}\),就轮到单调队列发挥作用了

在单调队列中,我要从尾部插入一个j,那我删除的那些节点的max就不用管了,而除了队尾,队列中的其他元素的max值也不会变(想一想,为什么),因此我只需在堆中更新队尾的max值
于是这道题就做完了

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<queue>
using namespace std;
#define int long long
inline int read()
{
	register int x=0,w=1;
	register char ch=getchar();
	while((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
	if(ch=='-'){ch=getchar();w=-1;}
	while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
	return x*w;
}
struct node{
	int id,val,y;
};//用结构体方便“懒惰删除” 
bool operator<(const node &x,const node &y){
	return x.val>y.val;
}
int q1[100005],l=1,r;
priority_queue<node>q2;
int n,m,st[100005][20],c[100005],a[100005],sum[100005];
int f[100005],used[100005],maxlog[100005];
int maxn(int l,int r)
{
	int p=maxlog[r-l+1];
	return max(st[l][p],st[r-(1<<p)+1][p]);
}
signed main()
{
    n=read();m=read();
    for(int i=1;i<=n;++i)
    {
    	a[i]=read();
    	if(a[i]>m){
    		cout<<-1<<endl;
    		return 0;
		}
    	sum[i]=sum[i-1]+a[i];
    	c[i]=lower_bound(sum,sum+i+1,sum[i]-m)-sum+1;
//    	if(c[i]==1) c[i]=0;
    	st[i][0]=a[i];
	}
	for(int len=1;len<=n;++len)
	{
		maxlog[len]=maxlog[len-1];
		if(1<<(maxlog[len]+1)<=len) maxlog[len]++;
	}
	for(int len=1;len<=maxlog[n];++len)
	{
		for(int i=1;i+(1<<len)-1<=n;++i)
		{
			st[i][len]=max(st[i][len-1],st[i+(1<<(len-1))][len-1]);
		}
	}
//	for(int i=1;i<=n;++i)
//	  cout<<sum[i]<<" ";
//puts("");
//	for(int i=1;i<=n;++i)
//	  cout<<sum[i]-m<<" ";
//puts("");
//	for(int i=1;i<=n;++i)
//	  cout<<c[i]<<" ";
    memset(f,0x3f,sizeof f);
    f[0]=0;
    for(int i=1;i<=n;++i)
    {
    	f[i]=min(f[i],f[c[i]-1]+maxn(c[i],i));
    	while(l<=r&&c[i]>q1[l]) l++,used[q1[l-1]]=1;
    	while(l<=r&&a[q1[r]]<=a[i]) r--,used[q1[r+1]]=1;
    	q1[++r]=i;
    	if(l<r)
    	q2.push((node){q1[r-1],f[q1[r-1]]+a[i],a[i]});
    	while(!q2.empty()&&(used[q2.top().id]||(q2.top().y<maxn(q2.top().id+1,i))))	
		q2.pop();	
		if(!q2.empty())  
    	f[i]=min(f[i],q2.top().val);
	}
//	for(int i=1;i<=n;++i)
//	  cout<<f[i]<<" ";
//	  
    cout<<f[n];
	return 0;
}
posted @ 2021-07-31 16:58  glq_C  阅读(176)  评论(0)    收藏  举报