题目传送门:https://www.luogu.org/problemnew/show/P1295
题目分析:这题是我在NOIP之前看到的,那个时候我们机房人人都在刷这题,而我因为在颓,就只好事后填坑啦。
按tututu的话来说,“这题不难想,其实怎么做都可以”,主要是因为有很多单调性。
首先 O(n2) 的DP很好推,记f[i]表示以第i本书为某一层结尾时,书架的最小高度。很明显:
其中对于每一个i,j的最小值是可以预处理出来的,记为Left[i],并且Left数组单调不降。
而且很明显f值也是单调不降的。而且若i固定,随着j越小,a[j]~a[i]的max值也是单调不降的,我们可以画个图:
很明显只有蓝色部分(即max值变化了的地方)的j值才有可能作为答案,我们只需要算这些地方贡献的最小值即可。那么假设新来了一个a[i+1]呢?
假设我们已经用一个双端队列存储了j1,j2,j3。我们发现j2~i,j3~i的max值小于a[i+1],于是将其弹出,最后将f[j2]+a[i+1]加进来。然后再考虑Left对转移的限制。比如j1小于Left[i],就要将其弹出队列,但最优答案可能会刚好取到Left[i],所以还要让它本身进入队列。同时用数据结构维护队列里贡献的最小值,时间 O(nlog(n)) 。
写代码的时候,有一个细节没有考虑好,就是如果Left[i]已经在队列里面,就不用再进队了,然而我判的时候没有考虑head>tail,即队列为空的情况,导致越界了。
CODE:
#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
using namespace std;
const int maxn=100100;
const int oo=1000000000;
int tree[maxn<<2];
int Left[maxn];
int Max[maxn];
struct data
{
int val,id,Time;
} h[maxn];
int sum[maxn];
int temp;
int que[maxn];
int g[maxn];
int head=1,tail=0;
int f[maxn];
int n,m;
bool Comp1(data x,data y)
{
return x.val<y.val;
}
bool Comp2(data x,data y)
{
return x.Time<y.Time;
}
int Query(int root,int L,int R,int x,int y)
{
if ( y<L || R<x ) return 0;
if ( x<=L && R<=y ) return tree[root];
int mid=(L+R)>>1;
int Left=root<<1;
int Right=Left|1;
int vl=Query(Left,L,mid,x,y);
int vr=Query(Right,mid+1,R,x,y);
return max(vl,vr);
}
void Update_max(int root,int L,int R,int x,int v)
{
if (L==R)
{
tree[root]=v;
return;
}
int mid=(L+R)>>1;
int Left=root<<1;
int Right=Left|1;
if (x<=mid) Update_max(Left,L,mid,x,v);
else Update_max(Right,mid+1,R,x,v);
tree[root]=max(tree[Left],tree[Right]);
}
void Update(int root,int L,int R,int x,int v)
{
if (L==R)
{
tree[root]=v;
return;
}
int mid=(L+R)>>1;
int Left=root<<1;
int Right=Left|1;
if (x<=mid) Update(Left,L,mid,x,v);
else Update(Right,mid+1,R,x,v);
tree[root]=min(tree[Left],tree[Right]);
}
int main()
{
freopen("1295.in","r",stdin);
freopen("1295.out","w",stdout);
scanf("%d%d",&n,&m);
for (int i=1; i<=n; i++) scanf("%d",&h[i].val),h[i].Time=i;
sort(h+1,h+n+1,Comp1);
temp=h[1].id=1;
for (int i=2; i<=n; i++)
if (h[i-1].val==h[i].val) h[i].id=temp;
else h[i].id=++temp;
sort(h+1,h+n+1,Comp2);
for (int i=1; i<=n; i++)
{
Max[i]=Query(1,1,temp,h[i].id,temp);
Update_max(1,1,temp,h[i].id,i);
}
sum[0]=0;
for (int i=1; i<=n; i++) sum[i]=sum[i-1]+h[i].val;
Left[1]=0;
for (int i=2; i<=n; i++)
{
Left[i]=Left[i-1];
while ( sum[i]-sum[ Left[i] ]>m ) Left[i]++;
}
for (int i=0; i<(n<<2); i++) tree[i]=oo;
Update(1,1,n+1,1,0);
que[++tail]=0;
g[tail]=0;
for (int i=1; i<=n; i++)
{
while ( head<=tail && que[tail]>=Max[i] )
Update(1,1,n+1,que[tail]+1,oo),tail--;
Update(1,1,n+1,Max[i]+1,f[ Max[i] ]+h[i].val);
tail++;
que[tail]=Max[i];
g[tail]=h[i].val;
int tp=oo;
while ( head<=tail && que[head]<Left[i] )
tp=g[head],Update(1,1,n+1,que[head]+1,oo),head++;
if ( tp<oo && ( que[head]!=Left[i] || head>tail ) )
head--,que[head]=Left[i],g[head]=tp,
Update(1,1,n+1,Left[i]+1,f[ Left[i] ]+tp);
f[i]=tree[1];
tail++;
que[tail]=i;
g[tail]=0;
Update(1,1,n+1,i+1,f[i]);
}
printf("%d\n",f[n]);
return 0;
}