P3957 [NOIP 2017 普及组] 跳房子

闲聊: \(n^2\) 过百万,暴力碾标算。Click here.

可见本题数据实在太水了,二分边界开小了反而跑过去了。


题目传送门

博客传送门

先判断一下无解的情况:显然应该是所有正得分的和小于 \(k\)。否则我们可以花上接近无限的金币,让这个机器人足够灵活,灵活到它可以只走遍所有得分为正的格子。

接下来考虑有解的情况。

首先有一个很易得的东西:最大得分随花的金币个数单调不降。

(这个很显然吧?对于一个花费金币较少的最大得分,即使金币增加,我们仍然能走原来的路线,当然也有了新的可能更好的路线,所以肯定单调不降)

那我们首先可以二分花费的金币个数。

然后压力给到 check 函数,它需要对于一个给定的范围区间 \([l,r]\) ,其中 \([l,r]\) 指位置 \(x\) 只能跳到 \([x+l,x+r]\) 中有房子的格子,求最大分数。

这个时候我们联想到一个题,题号P1725 ,正是本题去掉二分后的题面,也算是个弱化版。

我们设 \(dp_{i}\) 表示跳到第 \(i\) 个房子的最大得分。状态转移方程很显然,\(dp_{i}=\max\limits_{j=1}^{i-1}{[pos_{i}-r \le pos_{j} \le pos_{i}-l](dp_{j}+a_{i})}\)

其中 \(pos_{i}\) 表示第 \(i\) 个房子的位置。

因为本题的 \(pos\) 数组是升序输入的,所以我们发现 \(j\) 转移区间的左右端点都单调不降。于是我们可以用单调队列优化这个 \(dp\) 式子。

单调队列的板子题戳这里

于是我们就可以愉快地切掉这个蓝题了。

代码:

P3957
#include<bits/stdc++.h>
#define int long long
using namespace std;

inline int read(){
	int x=0,f=1;char c=getchar();
	while(c<48){
		if(c=='-') f=-1;
		c=getchar();
	}
	while(c>47) x=(x<<1)+(x<<3)+(c^48),c=getchar();
	return x*f;
}

inline void write(int x){
	if(x<0) putchar('-'),x=-x;
	if(x<10) putchar(x+'0');
	else write(x/10),putchar(x%10+'0');
}

const int N=5e5+5;
const int V=1e9;
const int inf=2e18;
int n,D,sco,dp[N],q[N];
struct Nahida{
	int pos,val;
}a[N];

inline bool check(int G){
	//类似P1725正解的check函数 
	for(int i=1;i<=n;i++){
		dp[i]=-inf;
	}
	int fr=1,tl=0;
	dp[0]=0;
	int dq=-1;
	for(int i=1;i<=n;i++){
		//警示后人:我这种写法,在加入队列时只判断了加入元素位置的右端点,所以应该先加入后删除,删掉不合法的解 
		//加入 
		while(dq+1<i&&a[dq+1].pos<=a[i].pos-max(1ll,D-G)){
			dq++;
			while(fr<=tl&&dp[q[tl]]<=dp[dq]){
				tl--;
			}
			q[++tl]=dq;
		}
		//删除 
		while(fr<=tl&&a[q[fr]].pos<a[i].pos-D-G){
			fr++;
		}
		if(fr>tl){
			dp[i]=-inf;
		}
		else{
			dp[i]=dp[q[fr]]+a[i].val;
		}
	}
	for(int i=1;i<=n;i++){
		if(dp[i]>=sco){
			return 1;
		}
	}
	return 0;
}

inline int erfen(){
	//二分花费金币的个数 
	int l=0,r=V;
	while(l<r){
		int mid=(l+r)>>1;
		if(check(mid)){
			r=mid;
		}
		else{
			l=mid+1;
		}
	}
	return l;
}

signed main(){
	n=read(),D=read(),sco=read();
	int sum=0;//sum需要开long long 
	for(int i=1;i<=n;i++){
		a[i].pos=read(),a[i].val=read();
		if(a[i].val>0) sum+=a[i].val;
	}
	a[0]={0,0};
	if(sum<sco){
		printf("-1");
		return 0;
	}
	int ans=erfen();
	printf("%lld",ans);
	return 0;
}
posted @ 2025-10-28 13:09  qwqSW  阅读(6)  评论(0)    收藏  举报