您的位置:首页 > 其它

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

2015-03-15 23:25 369 查看
题意:给定一个有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
,dp
,q
,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
);
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: