前言:
这是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;
}