洛谷 P3957 [NOIP2017 普及组] 跳房子(二分,单调队列优化dp)

传送门

第一年noip的考试题哇,满满的回忆
当年就因为这个题输出-1骗了5分拿了普及一等


解题思路

先二分答案,很显然答案满足单调性。
然后就是一个从限定范围转移过来的线性dp,用单调队列维护一下即可。
判断-1可以根据所有的正数加起来是否大于等于k。
细节还是很多的:

  1. dp数组初始化为一个很小的负数,防止有些跳不到的点在出现在了单调队列里
  2. 从零点开始枚举,这样第一个点跳不到也没问题
  3. 当求某个点dp值时发现队列为空,说明没有点能跳到这个点,continue即可(注意不是return 0)
  4. 注意更新队列顺序:先更新back,再更新head,再求当前点的dp值
  5. 注意每求得一个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

原文地址:https://www.cnblogs.com/yinyuqin/p/15200933.html