跳房子

好题笔记

跳房子

这一题是一可以说是二分答案的一个经典应用,想来讲一下不加优化的版本,也就是可以的一半的分。首先我们看到了最少的g,那么我们是不是就想到了二分答案,我们找出它可以到达的左端点(最开始赋值为1),之后找出可以到达的右边最大值(注意这里是max(d,a[n].z),因为有可能最开始就一下跳出去了),然后二分他,mid=(l+r)>>1
之后就开始判断,只要(l,mid)可以那么我们一定就去找更小的,如果不可以我们去找最大的,至于判断也很简单,我们发现肯定这一个格子是从上一个取得得分最大值的点跳过来的,那么用一个dp来记录没跳过得分就标记为负无穷,dp[0]=0,一波动态规划就欧克了。
这个简易版本的代码如下

#include<bits/stdc++.h>
using namespace std;
const long long neInf=0x8080808080808080;
struct ge{
	int w;
	int z;
};
ge a[500005];
int n,d,k;
int lr=1,rl;
long long f[500005];
bool check(int l,int r){//进来的这个值是终点
	memset(f,0x80,sizeof(f));
	f[0]=0;//最开始的方案是一步都不跳 
	for(int i=1;i<=n;i++){
		for(int j=0;j<i;j++){
			if(a[i].w-a[j].w>=l&&a[i].w-a[j].w<=r&&f[j]!=0x80)
				f[i]=max(f[i],f[j]+a[i].z);//我要跳这个格子肯定从得分最高的的跳来 
		}
		if(f[i]>=k) return true;
	}
	return false;
	
}


int main(){
	scanf("%d%d%d",&n,&d,&k);
	long long ans=0;
	for(int i=1;i<=n;i++){
		scanf("%d%d",&a[i].w,&a[i].z);
		if(a[i].z>0) ans+=a[i].z;
		rl=max(d,a[i].w);
	}
	if(ans<k) {
		printf("-1");
		return 0;
	}
	ans=0;
	a[0].z=0;
	a[0].w=0;
	while(lr<rl){
		int lcan;
		int rcan;
		int mid=(lr+rl)/2;
		//cout<<mid<<"  ";
		if(d-mid<1) lcan=1;
		else if(d-mid>=1) lcan=d-mid;
		rcan=mid+d;
		//cout<<rcan<<"   "<<lcan<<"   ";
		if(check(lcan,rcan)){
			ans=mid;
			rl=mid;
		//	cout<<ans<<endl;
		}
		else {
			lr=mid+1;
		}
		
	}
	printf("%lld",ans);
	return 0;
}

但是我们可以发现动态规划里有可以优化的东西,最先想到的肯定是记忆化搜索,但是我想了一想感觉难以下手,之后我就想到了优先队列这东东,因为我们每一次维护前一个最大值点都是符合一个一个区间的,那么我们完全可以用一个单调队列去维护这个区间最大值。
其实我们知道在dp枚举每一个点是后出来的点的dp值如果比前面的大那么我们完全没有必要去每一次找j前面那些点,所以我们用一个数组q去存单调对列里面都是哪些下表,每一次去维护他的递减性,并且发现队首的距离太远时去弹出队首,并每一次特判一下这个点到底可不可以到达,如果可以我们就把他在下一次查找i+1时入队,并且去判断前面那些的情况,如果无法到达,那么我们就不用去更改他的dp值
最后代码如下

	#include<bits/stdc++.h>
	using namespace std;
	const long long neInf=0x8080808080808080;
	struct ge{
		int w;
		int z;
	};
	ge a[500005];
	int n,d,k;
	int lr=1,rl;
	long long f[500005];
	int q[500005];//存单调队列;里面都是那些书的下标 
	bool check(int l,int r){//进来的这个值是终点
		memset(f,0x80,sizeof(f));
		memset(q,0,sizeof(q));
		f[0]=0;//最开始的方案是一步都不跳 
		int head=1;
		int last=0; 
		int j=0; //这里j不用每一次都更新,因为我们下一次就是会从上一个的基础上去找,同时我们还要利用j去吧j-1给入队
		for(int i=1;i<=n;i++){
			while(a[i].w-a[j].w>=l&&i>j){
				if(f[j]!=0x80){
					while(last>=head&&f[q[last]]<=f[j]) last--;//比这个点还要小的肯定就没用了,这些小的肯定在j之前,那么我之后肯定也就不用这些了,出队就OK 
					q[++last]=j;//把j入队 
				}
				j++;
			}
			while(a[i].w-a[q[head]].w>r&&head<=last) head++;//如果超出了就去掉 
			if(head<=last) f[i]=f[q[head]]+a[i].z;
			if(f[i]>=k) return true;
		}
		return false;
		
	}
	
	
	int main(){
		scanf("%d%d%d",&n,&d,&k);
		long long ans=0;
		for(int i=1;i<=n;i++){
			scanf("%d%d",&a[i].w,&a[i].z);
			if(a[i].z>0) ans+=a[i].z;
			rl=max(d,a[i].w);
		}
		if(ans<k) {
			printf("-1");
			return 0;
		}
		ans=0;
		a[0].z=0;
		a[0].w=0;
		while(lr<rl){
			int lcan;
			int rcan;
			int mid=(lr+rl)/2;
			if(d-mid<1) lcan=1;
			else if(d-mid>=1) lcan=d-mid;
			rcan=mid+d;
			if(check(lcan,rcan)){
				ans=mid;
				rl=mid;
			}
			else {
				lr=mid+1;
			}
			
		}
		printf("%lld",ans);
		return 0;
	}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值