noip前最后一篇博客了…
题面
题意:给出n个数,分成若干段,每段的和不能超过m,每段的代价为该段的最大值。问最小代价。
一个显然的dp,用f[i]表示前i个数,最后一段以i结尾的最小代价。
f[i]=min(f[j]+max(a[j+1,i]))
,其中sum[i]-sum[j]≤m。
正如小姐姐的美滋滋程度... f是非严格递增的,故只有对于a[j]为a[j]~a[i]的最大值的j,f[j]才有可能更新f[i]。
对于i,发现所有可能更新i的状态f[j],a[j]是非严格单调下降的。我们用个队列维护这个单调的a,把所有可能的j扔进一个机巧的堆里。
每次先维护单调性,再算答案。
#include <iostream>
#include <fstream>
#include <algorithm>
#include <cmath>
#include <ctime>
#include <cstdio>
#include <cstdlib>
#include <cstring>
using namespace std;
#define mmst(a, b) memset(a, b, sizeof(a))
#define mmcp(a, b) memcpy(a, b, sizeof(b))
typedef long long LL;
const int N=100100,oo=1e9+7;
int n,m,f[N],a[N],sum[N];
int q[N],hh=1,tt=1;
struct yy
{
int h[N],cnt;
void add(int x)
{
h[++cnt]=x;
int now=cnt;
while(now>1&&h[now>>1]>h[now])
swap(h[now],h[now>>1]),now>>=1;
}
void down()
{
h[1]=h[cnt];
cnt--;
int now=1;
while((now<<1)<=cnt)
{
int son=now<<1|1;
if(son>cnt||h[now<<1]<h[son])
son=now<<1;
if(h[son]<h[now])
swap(h[son],h[now]),now=son;
else
break;
}
}
}A,B;
int small()
{
while(A.h[1]==B.h[1]&&B.cnt)
{
A.down();
B.down();
}
return A.h[1];
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
scanf("%d",&a[i]),sum[i]=sum[i-1]+a[i];
a[0]=oo;
A.add(oo);
int now=0;
for(int i=1;i<=n;i++)
{
f[i]=oo;
while(sum[i]-sum[now]>m)
{
if(q[hh]==now)
{
if(hh<tt)
B.add(f[now]+a[q[hh+1]]);
hh++;
}
now++;
}
while(hh<=tt&&a[i]>a[q[tt]])
{
if(hh<tt)
B.add(f[q[tt-1]]+a[q[tt]]);
tt--;
}
q[++tt]=i;
if(hh<tt)
A.add(f[q[tt-1]]+a[i]);
if(now==q[hh])
f[i]=a[q[hh+1]]+f[now];
else
f[i]=a[q[hh]]+f[now];
if(hh<tt)
f[i]=min(f[i],small());
}
cout<<f[n]<<endl;
return 0;
}