【2017NOIP普及组】T4 跳房子

P3957 跳房子
题目传送门
思路:
对于题目所需求的g,因为要求最小,其实就是个二分查找的过程。然后对于g,我们每次都要做判断。
每次判断g的值是否能得到k分。过程其实可以理解为伪背包,意思是:对于一个点,在它前面的点如果能跳到它,那么就更新f[i],记得要取MAX,因为有负数。

bool check(long long g)
{
	memset(f,-127,sizeof(f));
	long long l=d-g,r=d+g;
	if(l<=0) l=1;
	f[0]=0;
	for(int i=1;i<=n;i++)
	{
		for(int j=i-1;j>=0;j--)
		{
			long long dis=(x[i]-x[j]);
			if(dis>r) break;
			if(dis<l) continue;
			f[i]=max(f[i],s[i]+f[j]);
			if(f[i]>=k) return 1;
		}
	}
	return 0;
}

如果不做优化,会有两个点超时。
网上大佬都是用优先队列对dp做优化,但我不会啊~
于是想想怎么去优化二分的右边界(减少二分的次数)。

我们只需要把r定到max(dd,d);
dd表示任意两个相邻的格子(格子都具有正整数分值)之间的最大距离。首先,我们能不去走负数的格子就尽量不去走负数的格子,这样就可以保证得分最高,但也会导致改造费用升高或不升(但最大值一定在此)那么我们考虑如何去只走正数格子,我们看到当 g >= d 的时候每次最多跳 g+d格,那么我们就可以保证一定能走到所有正数格子,我们就可以此缩小二分的区间,从而缩小时间复杂度啦!

#include<cstdio>
#include<iostream>
#include<cmath>
#include<cstring>
#include<string>
#include<algorithm>
#include<vector>
#define fre(x) freopen(#x".in","r",stdin),freopen(#x".out","w",stdout);
using namespace std;
const int MAX=2147483647;
const int N=5e5+10;
long long n,d,dd,k,x[N],s[N],f[N];
void input()
{
	scanf("%lld%lld%lld",&n,&d,&k);
	int temp=0;
	for(int i=1;i<=n;i++) 
	{
		scanf("%lld%lld",&x[i],&s[i]);
		if(s[i]>0) dd=max(dd,x[i]-x[temp]),temp=i;
	}
}
bool check(long long g)
{
	memset(f,-127,sizeof(f));
	long long l=d-g,r=d+g;
	if(l<=0) l=1;
	//for(int i=1;i<=n;i++) if(l<=x[i]&&x[i]<=r) f[i]=s[i];
	f[0]=0;
	for(int i=1;i<=n;i++)
	{
		for(int j=i-1;j>=0;j--)
		{
			long long dis=(x[i]-x[j]);
			if(dis>r) break;
			if(dis<l) continue;
			f[i]=max(f[i],s[i]+f[j]);
			if(f[i]>=k) return 1;
		}
	}
	return 0;
}
int main()
{
	//fre();
	input();
	long long l=0,r=max(d,dd),mid,ans=-1;
	while(l<=r)
	{
		mid=(l+r)/2;
		if(check(mid)) ans=mid,r=mid-1;
		else l=mid+1;
	}
	printf("%lld",ans);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/bigwinner888/article/details/105983158