poj 3017 Cut the Sequence(单调队列优化DP)

Cut the Sequence



$ solution: $

这道题出的真的很好,奈何数据水啊!

这道题当时看得一脸懵逼,说二分也不像二分,说贪心也不像贪心,说搜索吧这题数据范围怎么这么大?而且这题看起来也实在不好DP,当时是真的满头雾水。只能说是各个都尝试一下。最后还是选了DP来做第一步突破,因为这道题可以用最优子结构来推出最优答案,也符合常规DP套路即设 $ F[i][j] $ 表示将前面 $ i $ 个数分成 $ j $ 份,但是这一道题没有说具体的份数,而且数据范围很大,所以我们直接设 $ F[i] $ 表示前 $ i $ 个数的最优答案。这个的转移方程还是比较好想的:

$ F[i]=^{\quad\quad min}_{0<j<i,\sum^{k<i}_{k=j+1}A_k\leq M}{F[j]+max{A_k } } $

这样我们发现我们得到了一种 $ n^2 $ 的做法,然后数据范围告诉我们这样还不够。我们还得优化,但是 $ wch $ 是真的不会优化了,这个式子很有单调队列的样子,但是这个 $ max{A_k } $ ,这个式子似乎无懈可击啊!(于是他看了看书,表示:考这道题我直接爆零离场算了)

我们知道一个优秀的搜索或DP一定要尽可能去避免一些没有用的状态和决策,书上说:要保持决策集合的高度有效性和秩序性。所以我们看一看我们会不会枚举了一些没有什么用的 $ F[j] $ 。我们可以分析得出只有两种 $ F[j] $ 对我们有用:

  1. $ j $ 是满足 $ \sum^{k<i}_{k=j+1}A_k\leq M $ 的最小的 $ j $
  2. 我们的 $ A_j $ 是从 $ j $ 到 $ i $ 中间最大的权值

这个其实我们静下心来想想就可以明白,如果一个数不是从它开始到第 $ i $ 个数中最大的而且它不是可枚举的最小的第 $ j $ 个数,那么它一定不是最优解,它的左边那一个 $ F[j-1] $ 一定更优,因为它比 $ F[j ] $ 小,而且最大值一样。于是我们用维护一个包含所有的高效决策的单调队列,显然 $ j $ 单调递增 $ A_j $ 单调递减。然后我们要支持及时删去对头失效的决策,然后更新队尾加元素。

然后是这一题非常神奇的一个地方:我们要得到答案还需要 $ max{A_k } $ ,这个我们可以倍增 $ ST $ 表,但是我们分析题目性质就能发现一个跟奇妙的地方,我们的单调队列里的下一个元素就是我们要求得 $ max{A_k } $ ,这个只要想一想都知道以为我们维护的是(所有的高效决策的单调队列,并且显 $ j $ 单调递增 $ A_j $ 单调递减)。

然后还有一个我想吐槽的地方(奈何这一题数据水),我们的单调队列的对头不一定就是我们的最佳决策,这也是说我们队列里 $ A_K $ 单调递增,但是我们求值的式子里还有一个 $ F[j] $ 这个东西是单调递减的!!!!所以我们不能妄下决定,我们还需要映射一个取值的数据结构,也就是我们的 $ set $ ,我们要让这两个东西同时增加元素又同时减少相应的元素。



$ code: $

#include<iostream>
#include<cstdio>
#include<iomanip>
#include<algorithm>
#include<cstring>
#include<cstdlib>
#include<ctime>
#include<cmath>
#include<vector>
#include<queue>
#include<map>
#include<set>

#define ll long long
#define db double
#define inf 0x7fffffff
#define rg register int

using namespace std;

int n; ll m;
int a[100005];
ll f[100005];
int q[100005];
multiset<ll >s;

inline ll qr(){
	register char ch; register bool sign=0; ll res=0;
	while(!isdigit(ch=getchar())) if(ch=='-')sign=1;
	while(isdigit(ch)) res=res*10+(ch^48),ch=getchar();
	return sign?-res:res;
}

int main(){
	//freopen("in.in","r",stdin);
	//freopen("cpp.out","w",stdout);
	n=qr(); m=qr();
	for(rg i=1;i<=n;++i) a[i]=qr();
	rg l=1,r=0,k=1; ll tot=0;
	for(rg i=1;i<=n;++i){
		tot+=a[i];
		while(tot>m)tot-=a[k++];
		if(k>i){puts("-1");return 0;}
		while(l<=r&&a[q[r]]<=a[i]){
			if(r>l)s.erase(f[q[r-1]]+a[q[r]]);; --r;
		}q[++r]=i; if(r>l)s.insert(f[q[r-1]]+a[q[r]]);
		while(l<=r&&q[l]<k){
			if(r>l)s.erase(f[q[l]]+a[q[l+1]]);; ++l;
		}f[i]=f[k-1]+a[q[l]];
		if(l<r)f[i]=min(f[i],*s.begin());
	}printf("%lld\n",f[n]);
	return 0;
}
posted @ 2019-06-11 15:56  一只不咕鸟  阅读(301)  评论(0编辑  收藏  举报