• 洛谷luogu3957跳房子(单调队列优化)


    QwQ被普及组的题折磨的死去活来。

    硬是卡线段树,没卡过QwQ

    oi生涯,第一道正经的单调队列dp题

    进入正题

    题目大意:
    这里写图片描述

    其中(n le 500000)

    看到这个题的第一感觉就是二分金币数

    很显然,如果(mid)可以,那么(mid+1)一定可以

    但是QwQ

    不要想(nlog^2n)了(卡了一下午没卡过去)

    首先考虑裸的(dp)

    (dp[i]=max(dp[j])+val[i])


    其中(mindis<=|dis[i]-dis[j]|<=maxdis)

    这种做法应该是(O(n^2logn))

    这里就不放代码了

    取max的过程可以用线段树来优化QwQ虽然和暴力一个分 但是我只写了这种暴力。。。放一波代码

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<cmath>
    
    using namespace std;
    
    inline int read()
    {
       int x=0,f=1;char ch=getchar();
       while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
       while (isdigit(ch)){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
       return x*f;
    }
    
    const int maxn = 1000010;
    
    int x[maxn],val[maxn];
    int n,d,k;
    int dp[maxn],f[4*maxn];
    int add[4*maxn];
     
    void up(int root)
    {
        f[root]=max(f[2*root],f[2*root+1]);
    }
    
    void pushdown(int root,int l,int r)
    {
        if (add[root])
        {
            add[2*root+1]=add[root*2]=add[root];
            f[2*root]=add[root];
            f[2*root+1]=add[root];
            add[root]=0;
        }
    }
    
    void build(int root,int l,int r)
    {
        add[root]=0;
        if (l==r)
        {
            f[root]=-1;
            return;
        }
        int mid =(l+r) >> 1;
        build(2*root,l,mid);
        build(2*root+1,mid+1,r);
        up(root);
    }
    
    void update(int root,int l,int r,int x,int y,int p)
    {
        if (l>r || x>y) return;
        if (x<=l && r<=y)
        {
            add[root]=p;
            f[root]=p;
            return;
        }
        int mid = (l+r) >>1;
        pushdown(root,l,r);
        if (x<=mid) update(2*root,l,mid,x,y,p);
        if (y>mid) update(2*root+1,mid+1,r,x,y,p);
        up(root);
    }
    
    int query(int root,int l,int r,int x,int y)
    {
        if (l>r || x>y) return -1;
        if (x<=l && r<=y)
        {
            return f[root];
        }
        pushdown(root,l,r);
        int mid = (l+r) >> 1;
        int ans=0;
        if (x<=mid) ans=max(ans,query(2*root,l,mid,x,y));
        if (y>mid) ans=max(ans,query(2*root+1,mid+1,r,x,y));
        return ans;
    }
    
    bool check(int min1,int max1)
    {
        build(1,1,n);
        memset(f,-1,sizeof(f));
        memset(dp,-1,sizeof(dp));
        int l=1,r=1;
        //int beg=1;
        dp[1]=0;
        //cout<<1<<endl;
        for (int i=2;i<=n+1;i++)
        {
            //cout<<1<<endl;
            while (x[i]-x[r] >= min1) update(1,1,n,r,r,dp[r]),r++;
            while (x[i]-x[l]>max1) update(1,1,n,l,l,-1),l++;
            //if (l>r) continue;
            //cout<<1<<endl;
            int tmp = query(1,1,n,1,n);
            if (tmp!=-1)  
            {
                dp[i]=max(dp[i],tmp+val[i]);
            }
        }
        int mx=-1e9;
        for (int i=1;i<=n+1;i++) mx=max(dp[i],mx);
        //for (int i=1;i<=n+1;i++) cout<<dp[i]<<" ";
       // cout<<endl;
        if (mx>=k) return true;
        else return false; 
    }
    
    int main()
    {
      n=read(),d=read(),k=read();
      for (int i=2;i<=n+1;i++) x[i]=read(),val[i]=read();
      int l=1,r=1e9;
      int ans=0; 
      while (l<=r)
      {
      	 int mid =(l+r) >> 1;
      	 if (check(max(d-mid,1),d+mid)) r=mid-1,ans=mid;
      	 else l=mid+1;
      	 //cout<<l<<" "<<r<<endl;
      }
      if (ans==0) ans=-1; 
      cout<<ans<<endl;
      return 0;
    }
    

    真正的正确做法 是使用单调队列优化

    单调队列是什么呢?

    是一个队列 对,队列中的元素都是单调的

    就比如说(3,2,1)

    单调队列在维护的时候有两个注意:
    1.新进来元素,会将他之前连续的比他小的元素代替掉,直到遇到一个大的,或者队列为空 (可以理解为过期晚,决策更优秀)

    2.注意(head)(tail)的大小和加减

    直接上代码

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<cmath>
    #define ll long long
    
    using namespace std;
    
    inline ll read()
    {
       ll x=0,f=1;char ch=getchar();
       while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
       while (isdigit(ch)){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
       return x*f;
    }
    
    const int maxn = 1000010;
    
    ll x[maxn],val[maxn];
    ll n,d,k;
    ll dp[maxn];
    ll q[maxn];
    ll t[maxn];
    ll l=1,r=1;
    ll head=1,tail=0; 
    
    void push(int now,ll min1,ll max1)
    {
        while (x[now]-x[r]>=min1) 
        {
            while (dp[r]>q[tail] && head<=tail) q[tail]=t[tail]=0,tail--;
            q[++tail]=dp[r];
            t[tail]=x[r];
            r++;
        }
        while (x[now]-t[head]>max1 && head<=tail) t[head]=0,q[head++]=0; 
    }
    bool check(ll min1,ll max1)
    {
        memset(dp,0xdf,sizeof(dp)); 
        l=1,r=1;
        dp[1]=0;
        head=1,tail=0;
        for (int i=2;i<=n+1;++i)
        {
            push(i,min1,max1);
            if (head<=tail)  
            {
                dp[i]=max(dp[i],q[head]+val[i]);
            }
        }
        ll mx=-1e9;
        for (int i=1;i<=n+1;i++) mx=max(dp[i],mx);
        if (mx>=k) return true;
        else return false; 
    }
    
    int main()
    {
      n=read(),d=read(),k=read();
      for (int i=2;i<=n+1;i++) x[i]=read(),val[i]=read();
      ll l1=1,rr=6e9;
      ll ans=0; 
      while (l1<=rr)
      {
      	 ll mid =(l1+rr) >> 1;
      	 if (check(max(d-mid,(long long)1),d+mid)) rr=mid-1,ans=mid;
      	 else l1=mid+1;
      	 //cout<<l<<" "<<r<<endl;
      }
      if (ans==0) ans=-1; 
      cout<<ans<<endl;
      return 0;
    }
     
    
    
  • 相关阅读:
    猎户、双子、英仙
    第二卦,还叫我保持现状?
    昨晚的第三卦,就快万劫不复了
    明天要出去办事,看看情况
    luogu P3939 数颜色 |vector
    luogu P2701 [USACO5.3]巨大的牛棚Big Barn |动态规划
    luogu P2345 奶牛集会 |排序+树状数组
    luogu P4943 密室 |最短路
    luogu P4343 [SHOI2015]自动刷题机 |二分答案
    luogu P3110 [USACO14DEC]驮运Piggy Back |最短路
  • 原文地址:https://www.cnblogs.com/yimmortal/p/10160852.html
Copyright © 2020-2023  润新知