欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

洛谷P1848 [USACO12OPEN]书架Bookshelf

程序员文章站 2022-06-02 21:41:01
...

https://www.luogu.org/problem/show?pid=1848
这道题目的线段树简直就是水题,然后用set去做,日了狗了,搞了1个小时左右结果只过4个点,标算又看不懂,先把坑挖好;
首先这道题目的暴力dp那是显然的
f[i]可以从f[j-1]+max(h[j]……h[i])推得
不难发现无论是f[]还是当j确定的max(h[j]……h[i]),都是单调的;
那么我们大力线段树;
对于一个新加入的i点
简单来说就是维护h[j]=max(h[j]……h[i]),顺便吧答案维护了;
然后单调队列获得一段答案的可行区间last~i
那么在这段区间里取答案;
最后把答案放到f[i+1]
换句话说一开始的f[1]是0
这样的话每一个叶子节点的答案刚刚好就是f[j]+h[j]

#include<bits/stdc++.h>
#define Ll long long
using namespace std;
const int N=1e5+5;
struct tree{int l,r,h,H,lazy;Ll f,v;}T[N*4];
int q[N],last;
int n,L,x,y;
Ll sum,ans;
void make(int id,int l,int r){
    T[id].l=l;T[id].r=r;
    if(l==r)return;
    int mid=l+r>>1;
    make(id*2  ,l,mid  );
    make(id*2+1,mid+1,r);
}
void push(int id){
    if(T[id].lazy==0||T[id].l==T[id].r)return;
    int x=id*2,y=x+1;
    T[x].H=T[x].h=T[y].H=T[y].h=T[x].lazy=T[y].lazy=T[id].lazy;
    T[x].v=T[x].f+T[x].H;T[y].v=T[y].f+T[y].H;T[id].lazy=0;
}
void change(int id,int l,int r,int x){
    push(id);
    if(l<=T[id].l&&T[id].r<=r){
        if(T[id].H<x){
            T[id].H=T[id].h=T[id].lazy=x;
            T[id].v=T[id].f+x;
        }else{
            if(T[id*2  ].h<x)change(id*2  ,l,r,x);
            if(T[id*2+1].h<x)change(id*2+1,l,r,x);
            T[id].H=max(T[id*2].H,T[id*2+1].H);
            T[id].h=min(T[id*2].h,T[id*2+1].h);
            T[id].v=min(T[id*2].v,T[id*2+1].v);
        }return;
    }
    if(T[id*2  ].r>=l)change(id*2  ,l,r,x);
    if(T[id*2+1].l<=r)change(id*2+1,l,r,x);   
    T[id].H=max(T[id*2].H,T[id*2+1].H);
    T[id].h=min(T[id*2].h,T[id*2+1].h);
    T[id].v=min(T[id*2].v,T[id*2+1].v);
}
Ll out(int id,int l,int r){
    push(id);
    if(l<=T[id].l&&T[id].r<=r){
//        if(l==4&&r==5)cout<<id<<' '<<T[id].h<<endl;
        return T[id].v;
    }
    Ll ans=1e18;
    if(T[id*2  ].r>=l)ans=min(ans,out(id*2  ,l,r));
    if(T[id*2+1].l<=r)ans=min(ans,out(id*2+1,l,r));
    return ans;
}
void add(int id,int x,Ll f){
    push(id);
    if(T[id].l==T[id].r){T[id].f=f;return;}
    if(T[id*2].r>=x)add(id*2,x,f);else add(id*2+1,x,f);
    T[id].f=min(T[id*2].f,T[id*2+1].f);
}
int main()
{
    scanf("%d%d",&n,&L);
    last=1;
    make(1,1,n);
    for(int i=1;i<=n;i++){
        scanf("%d%d",&x,&q[i]);
        sum+=q[i];
        while(sum>L)sum-=q[last++];
        change(1,1,i,x);
        ans=out(1,last,i);
        if(i!=n)add(1,i+1,ans);
    }printf("%lld",ans);
}

那什么单调队列那我明天学习吧,真的是日了狗了


单调队列调出来了哈哈;
感觉单调队列并没有数据结构那么暴力那么好打;
这种做法完全依赖于这道题目的单调性;
首先先说几点
我的写法貌似并不是很快,而且在不开O2的情况下是比线段树还要慢点;
开了o2就是很快的啦;
然后对于这个set我每进进行一次插入删除,那么就需要一个log的时间,我的程序是带有很大的常数的,但感觉也是基本的一种做法;

对于基本的n^2的dp我就不在赘述了;
那么我们对于每一个读入i
首先就是需要确定可向左边扩展的最远点last
然后根据h[i]去更新这段区间里的h[j];
然后把last~i这段区间的答案全放到一个set里面去
最后去一个最小值产生一个新的f[i];
于是我开了一个队列q
代表现在last~i这段区间里,高度为h[j]最靠前的下标
因为当i确定的时候,h[j]>=h[j+1]
并且h[j]有很多相同的,那么我们要把h[j]合并起来
那么我们对于一个i,先把i放到q里面,然后不断找h[q[top]]<=h[i]把他弹出
之后再把h[j]<=h[i]的最靠前的重新插入,这样的话我们就可以维护关于h[j]了
然后我们考虑如何删除到不了的点,就是更新last值
首先我们判断当前的点是否在q里面,如果在的话就从队首弹出,不在的话,我们把h[j]跟新为h[j-1]
为什么呢,因为我们维护h[j]相同的一块的时候我们只是更新了h[j]值相同的最前面的一点,并且把这个点放到了q里面,那么之后的点的h[j]值其实就是h[j-1],
最后我们要看看现在的last值是不是在队列里面,如果不在的话要从队首压入;
如果没听懂那么很抱歉,我的语文水平貌似太差了
其实就是找到单调性的h[j]规律而已
至于那个in[j]就代表j有没有在q里面

#include<bits/stdc++.h>
#define Ll long long
using namespace std;
const Ll N=1e5+5;
Ll h[N],w[N],f[N],q[N],top;
bool in[N];
multiset<Ll>S;
Ll n,m,last,sum,l;
int main()
{
    scanf("%lld%lld",&n,&m);
    l=last=1;in[1]=1;
    for(Ll i=1;i<=n;i++){
        scanf("%lld%lld",&h[i],&w[i]);
        sum+=w[i];
        while(sum>m){
            if(in[last])S.erase(f[last-1]+h[last]),l++;else h[last]=h[last-1];
            in[last]=0;sum-=w[last++];    
        }
        if(!in[last]){
            in[last]=1;q[--l]=last;h[last]=h[last-1];
            S.insert(f[last-1]+h[last]);
        }
        q[++top]=i;S.insert(f[i-1]+h[i]);in[i]=1;
        while(top>=l&&h[q[top]]<=h[i]){
            in[q[top]]=0;
            S.erase(f[q[top]-1]+h[q[top]]);
            top--;        
        }
        in[q[++top]]=1;
        h[q[top]]=h[i];
        S.insert(f[q[top]-1]+h[q[top]]);
        f[i]=*S.begin();
    }
    printf("%lld",f[n]);
}