poj 3017 dp+单调队列(拆分序列)

题意:给定一个有n个数字的序列,现要将此序列分成几段,使得每段数字的和不大于给定的数字m。并且求使得各段和的最大值最小。

思路:一个n^2的dp比较容易想到。令f[i] 表示前i个数按照题目要求的最小的和,则必然有f[i] = min(f[j] + max(a[j +1 , a[j + 2].....a[i])) ,其中j<= i,j的位置还得满足题目中m的限制。由于a数组都是大于0的,所以可以发现f必然是非递减的。设a[j + 1], a[j + 2], ...a[i]中值最大的下标为k,设x为[j + 1,k]的任意一个下标,则a[x],a[x+1],....a[i]的最大值的下标显然也是k了。由f的非递减性,f[j+1] + a[k] <= f[j+2]+a[k].....<= f[k - 1] + a[k] 。很显然,我们只要取f[j+1]+a[k]就可以了。也就是说如果某一段到当前i位置的最大值都一样,取最靠前的即可。
如何维护呢,可以联想到单调队列。维护一个递减的队列,存的是符合要求的某一段的最大值,但是可以发现,并不是队首元素就是最优,因为队列中的递减性质,队列中的所有元素都有可能导致最优解。

#include <stdio.h>
#include <string.h>
#define min(a,b) ((a)<(b)?(a):(b))
#define N 100005
int n;
__int64 m,sum=0;
int s[N],dp[N],q[N],front,rear;
int main(){
	int i,j,k,flag = 1;
	freopen("a.txt","r",stdin);
	scanf("%d %I64d",&n,&m);
	for(i = 1;i<=n;i++){
		scanf("%d",&s[i]);
		if(s[i]>m)
			flag = 0;
	}
	if(!flag)
		printf("-1\n");
	else{
		dp[0] = 0;
		front = rear = -1;	
		for(i = j = 1;i<=n;i++){
			sum += s[i];
			while(sum > m)
				sum-=s[j++];//找到当前段和不大于m的起点
			while(front < rear && q[front+1]<j)
				front++;
			while(front < rear && s[q[rear]]<=s[i])//更新队列
				rear--;
			q[++rear] = i;
			dp[i] = dp[j-1]+s[q[front+1]];
			for(k = front+2;k<=rear;k++)
				dp[i] = min(dp[i], dp[q[k-1]]+s[q[k]]);
		}
		printf("%d\n",dp[n]);
	}
	return 0;
}


你可能感兴趣的:(poj 3017 dp+单调队列(拆分序列))