[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;
}