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;
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 手把手教你在本地部署DeepSeek R1,搭建web-ui ,建议收藏!
· 新年开篇:在本地部署DeepSeek大模型实现联网增强的AI应用
· Janus Pro:DeepSeek 开源革新,多模态 AI 的未来
· 互联网不景气了那就玩玩嵌入式吧,用纯.NET开发并制作一个智能桌面机器人(三):用.NET IoT库
· 【非技术】说说2024年我都干了些啥