zoukankan      html  css  js  c++  java
  • Luogu P3957 跳房子

    题面

    前言:

    这是2017年普及组T4,结合2018年的T3,以及NOI online 2020 T2 可以看出NOIP普及组已经对DP的数据结构优化有一定要求了

    正文

    作为一道考场题,它居然很难打暴力

    首先,看到数据范围,找出我们将要枚举的两个数据 (1leq n leq 500000),(1leq x_i leq 10^9)

    考虑到很难由 (k) 推出最终答案,只能由答案反推出是否能达到 (k)

    看到这个答案范围考虑二分,当然氪的钱越多,性能越强,答案满足单调性

    接下来的难点就是如何用已有条件求出花费

    都这样了你还不DP??

    DP目标复杂度预定为 (O(n))

    但这道题看起来不简单先想 (O(n^2)) 的方法

    首先 (x_i) 具有单调性,省了一个sort

    以每一个 (x_i) 作为一个节点, (f_i) 表示前 (i) 个节点的最优解,转移方程:

    [f_i = max_{i-(d+g)leq j leq i-(d-g)}f_j+s_i ]

    如果 (f_ngeq k) 那么符合条件

    好了你50分有了(这看起来都不像暴力)

    接下来就要优化了

    因为我们的目标复杂度是 (O(n)) ,所以要确保只有每个状态只有一次转移

    即在 (O(1)) 的时间下求得下标为 ([i-(d+g),i-(d-g)]) 范围间的 (f) 的最大值

    类似优化的题目很多,CCF又偷懒,锁定目标单调队列

    将有用的状态放入队列,没用或不合法的弹出即可

    核心代码就敲出来了

    inline bool check(int g) {
        memset(f,0xcf,sizeof f);             // 初始化最小值
        memset(q,0,sizeof q);
        int l = d-g > 0 ? d-g : 1,r = d+g;   //  l,r 圈定范围
        h = 1,t = 0;                         //  队列头尾
        f[0] = 0;
        long long inf = f[1];
        for(int i=1,j=0;i<=n;++i) {          //  i表示现在要求的状态,j表示判断过能否过队列的序号
            while(x[i]-x[j]>=l && i>j) {     //  若在范围内,继续枚举
                if(f[j] != inf) {            //  状态不合法
                    while(h<=t && f[q[t]] <= f[j]) --t;
                    q[++t] = j;
                }
                ++j;                         //  继续枚举,∵i>j,∴j最大到n-1 
            }
            while(h<=t && x[i]-x[q[h]]>r) ++h;   // 超出范围,弹出队列
            if(h<=t) f[i] = f[q[h]] + s[i];      // 状态转移
            if(f[i] >= k) return true;           // 若达到标准,返回true
        }
        return false;
    }
    

    最终代码:

    #include<bits/stdc++.h>
    using namespace std;
    const int maxn = (int)5e5+7;
    char ch;int fl;
    template<typename T>
    inline T redn(T &ret) {                                        // 快读
        ret = 0,fl = 1,ch = getchar();
        while(ch<'0' || ch>'9') {if(ch=='-') fl=-1;ch = getchar();}
        while(ch>='0'&&ch<='9') {ret=ret*10+ch-'0';ch=getchar();}
        return ret=ret*fl;
    }
    int n;
    long long x[maxn],s[maxn],d,k;
    long long f[maxn],q[maxn],h,t;
    inline bool check(int g) {
        memset(f,0xcf,sizeof f);
        memset(q,0,sizeof q);
        int l = d-g > 0 ? d-g : 1,r = d+g;
        h = 1,t = 0;
        f[0] = 0;
        long long inf = f[1];
        for(int i=1,j=0;i<=n;++i) {
            while(x[i]-x[j]>=l && i>j) {
                if(f[j] != inf) {
                    while(h<=t && f[q[t]] <= f[j]) --t;
                    q[++t] = j;
                }
                ++j;
            }
            while(h<=t && x[i]-x[q[h]]>r) ++h;
            if(h<=t) f[i] = f[q[h]] + s[i];
            if(f[i] >= k) return true;
        }
        return false;
    }
    int main() {
        redn(n),redn(d),redn(k);
        long long tmp=0,mx=0;
        for(int i=1;i<=n;++i) {
            redn(x[i]),redn(s[i]);
            if(s[i] > 0)tmp += s[i];
        }
        if(tmp<k) return printf("-1"),0;        // 若大于0的分数加起来都小于k,则无解,可惜他CCF数据没有无解
        int l=0,r=x[n],mid,xx;
        while(l<=r) {	
            mid = (l+r) >> 1;
            if(check(mid)) {r = mid-1;xx=mid;}  // 若 mid check 成功了,就记录
            else l = mid+1;
        }
        printf("%d",xx);
        return 0;
    }
    
  • 相关阅读:
    奇虎360安全牛人全球挑战赛无线部…
    Portugal 2 1 minute has Pipansihuan Germany and USA tacit or kick the ball
    求最大公约数和最小公倍数
    JQuery的Ajax跨域请求的解决方式
    从Java到C++——从union到VARIANT与CComVariant的深层剖析
    抽卡概率的測试
    jquery序列化表单以及回调函数的使用
    Notepad++插件安装和使用和打开大文件
    Android开发遇到的问题
    bzoj3068: 小白树
  • 原文地址:https://www.cnblogs.com/Ax-Dea/p/12535520.html
Copyright © 2011-2022 走看看