洛谷P1295:[TJOI2011]书架 (线段树优化DP)

来源:互联网 发布:java finalize 异常 编辑:程序博客网 时间:2024/06/05 15:42

题目传送门:https://www.luogu.org/problemnew/show/P1295


题目分析:这题是我在NOIP之前看到的,那个时候我们机房人人都在刷这题,而我因为在颓,就只好事后填坑啦。
按tututu的话来说,“这题不难想,其实怎么做都可以”,主要是因为有很多单调性。

首先O(n2)的DP很好推,记f[i]表示以第i本书为某一层结尾时,书架的最小高度。很明显:

f[i]=min(f[j]+max(a[j+1],a[j+2]a[i]))[a[j+1]++a[i]<=m]

其中对于每一个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;}