[POJ 3017] 单调队列优化DP

[POJ 3017]

Problem

  • 给定一个长度为N的序列A,要求把该序列分成若干段,在满足每段中所有数值和不超过M的前提下,让每段中所有数的最大值之和最小。
  • N<=10^5,M<=10^11,0<=Ai<=10^6

Solution

  • 很容易写出状态转移方程
  • F[i]=  min  { F[j] + max(Ak) }   (j+1<=k<=i)
  • 考虑什么状态能更新当前答案
  • 对于当前点i,它能到达的最左边的点是j,设X=max{ Aj ~ Ai }
  • F数组值单调递增
  • F[j-1]+X <= F[j]+X <= F[j+1]+X <= F[pos]+X
  • 单调队列维护即可

Code


#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define rep(i,a,b) for(ll i=(a);i<=(b);i++)
#define ll long long
using namespace std;
const ll N=2e6;
ll a[N],q[N],f[N],now=0,tail,head,l,r,n,m;
int main()
{
	//freopen("a.in","r",stdin);
	memset(f,0x3f,sizeof(f));f[0]=0;
	scanf("%lld%lld",&n,&m);
	tail=0,head=1,l=0;
	rep(i,1,n){
		scanf("%lld",&a[i]);
		if(a[i]>m){cout<<-1;return 0;}
		now+=a[i];
		while(now>m)now-=a[++l];
		while(head<=tail&&a[q[tail]]<=a[i])tail--;
		q[++tail]=i;
		while(head<=tail&&q[head]<l)head++;
		ll pos=l;//注意
		rep(j,head,tail){
			f[i]=min(f[i],f[pos]+a[q[j]]);
			pos=q[j];
		}
		
	}
	cout<<f[n];
	return 0;
}

猜你喜欢

转载自blog.csdn.net/strangeDDDF/article/details/87908029