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

题目传送门: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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值