1. 程式人生 > 其它 >洛谷 P3957 [NOIP2017 普及組] 跳房子(二分,單調佇列優化dp)

洛谷 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