Luogu P3957 跳房子

题面

前言:

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

正文

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

首先,看到数据范围,找出我们将要枚举的两个数据 \(1\leq n \leq 500000\),\(1\leq 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_n\geq 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;
}
posted @ 2020-03-20 22:48  AxDea  阅读(95)  评论(0编辑  收藏  举报