洛谷 P3957 [NOIP2017 普及组] 跳房子(二分,单调队列优化dp)
传送门
第一年noip的考试题哇,满满的回忆
当年就因为这个题输出-1骗了5分拿了普及一等
解题思路
先二分答案,很显然答案满足单调性。
然后就是一个从限定范围转移过来的线性dp,用单调队列维护一下即可。
判断-1可以根据所有的正数加起来是否大于等于k。
细节还是很多的:
- dp数组初始化为一个很小的负数,防止有些跳不到的点在出现在了单调队列里
- 从零点开始枚举,这样第一个点跳不到也没问题
- 当求某个点dp值时发现队列为空,说明没有点能跳到这个点,continue即可(注意不是return 0)
- 注意更新队列顺序:先更新back,再更新head,再求当前点的dp值
- 注意每求得一个dp,都需要判断一下是否满足条件(题意中说可以随时停下)
AC代码
#include<cstdio>
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cmath>
#include<algorithm>
#include<deque>
using namespace std;
const int maxn=500005;
const int maxx=1e9;
int n,k,d,l,r=maxx,x[maxn],s[maxn];
long long sum,dp[maxn];
bool check(int mid){
deque<int> q;
int ss=max(1,d-mid),t=min(maxx,d+mid),now=0;
memset(dp,-0x3f,sizeof(dp));
dp[0]=0;
for(int i=0;i<=n;i++){
while(now<i&&x[i]-x[now]>=ss){
while(!q.empty()&&dp[now]>dp[q.back()]) q.pop_back();
q.push_back(now);
now++;
}
while(!q.empty()&&x[i]-x[q.front()]>t) q.pop_front();
if(q.empty()) continue;
dp[i]=dp[q.front()]+s[i];
if(dp[i]>=k) return 1;
}
return 0;
}
int main(){
ios::sync_with_stdio(false);
cin>>n>>d>>k;
for(int i=1;i<=n;i++){
cin>>x[i]>>s[i];
if(s[i]>0) sum+=s[i];
}
if(sum<k){
cout<<"-1"<<endl;
return 0;
}
while(l<r){
int mid=(l+r)/2;
if(check(mid)) r=mid;
else l=mid+1;
}
cout<<l;
return 0;
}
//NOIP2017普及组 t4