P3957 跳房子

P3957 跳房子

祭奠一下逝去的时间


题解

50pt Solution

看到这个题直觉想到是DP 但是不会写

我们可以二分答案,花费金币为 \(mid\) ,二分枚举 \(mid\)

我们设置 \(f[i]\) 为到达第 \(i\) 个节点时的最大得分,由于跳房子是一直往右跳的,所以 \(f[i]\) 一定是由它之前的节点中得分最大的转移过来的,所以有:

for(int i=1;i<=n;i++)
	  for(int j=i-1;j>=0;j--){
	  	if(x[i]-x[j]>=lef&&x[i]-x[j]<=rig)
	  	   f[i]=max(f[i],f[j]+s[i]);
        if(f[i]>k) return 1;
	  }

但是这题数据太大了。。。\(n^2\) 只能 \(50pt\)

\(code\)

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<string>
#include<cstring>
#include<queue>
using namespace std;

typedef long long ll;

inline int read()
{
	int ans=0;
	char last=' ',ch=getchar();
	while(ch<'0'||ch>'9') last=ch,ch=getchar();
	while(ch>='0'&&ch<='9') ans=ans*10+ch-'0',ch=getchar();
	if(last=='-') ans=-ans;
	return ans;
}

const int maxn=5e5+10,inf=-1e9+10;
int n,d,k;
int s[maxn],x[maxn];
int l=0,r=0,mid=0;
ll f[maxn];
int ans;

bool check(int mid)
{
	for(int i=1;i<=n;i++) f[i]=inf;
	int lef=max(1,d-mid);
	int rig=d+mid;
	for(int i=1;i<=n;i++)
	  for(int j=i-1;j>=0;j--){
	  	if(x[i]-x[j]<lef) continue;
	  	if(x[i]-x[j]>rig) break;
	  	f[i]=max(f[i],f[j]+s[i]);
	  	if(f[i]>k) return 1;
	  }
	return 0;
}

int main()
{
	n=read();d=read();k=read();
	for(int i=1;i<=n;i++){
		x[i]=read();s[i]=read();
		if(s[i]>0) ans+=s[i];
		r=max(r,x[i]);
	}
	if(ans<k){
		printf("-1\n");
		return 0;
	}
	while(l<r){
		mid=(l+r)>>1;
		if(check(mid)) r=mid;
		else l=mid+1;
	}
	printf("%d\n",r);
	
	return 0;
}

100pt Solution

满分要二分+DP+单调队列

我们又上面可知

\(f[ i ]=max( f[j] )+s[i] ,j<i\)

而且随着 \(i\) 不断往前跳,向前更新,\(j\) 一定会有不在弹跳范围内的,所以由单调性可知可以使用单调队列

队列队首维护使得 $f[i] $ 最大的那个点下标

我们枚举 \(j\) ,只需要枚举一遍就行了,\(j\) 是那个待加入的点的下标,如果它在弹跳范围内,而且下标小于 \(i\) ,就把它加入到队列中的合适位置,加入之后仍然保持队列单调性,弹出超出合法弹跳区间的点,如果队列非空就更新 $f[i] $ 的值

主体部分:

bool check(ll mid)
{
	q.clear() ;
	for(int i=1;i<=n;i++) f[i]=inf;f[0]=0;
	ll lef=(d-mid<0?1:d-mid);
	ll rig=d+mid;
	int j=0;
	for(int i=1;i<=n;i++){
		while(x[i]-x[j]>rig) j++;
		while(x[i]-x[j]<=rig&&x[i]-x[j]>=lef&&j<i){
			while(!q.empty() &&(f[j]>=f[q.back() ]||x[i]-x[q.back()]>rig)) q.pop_back() ;
			q.push_back(j) ;
			j++;
		}
		while(!q.empty() &&x[i]-x[q.front() ]>rig) q.pop_front() ;
		if(!q.empty()) f[i]=f[q.front()]+s[i];
		if(f[i]>=k) return 1;
	}
	return 0;
}

\(code\)

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<string>
#include<cstring>
#include<queue>
using namespace std;

typedef long long ll;

inline ll read()
{
	ll ans=0;
	char last=' ',ch=getchar();
	while(ch<'0'||ch>'9') last=ch,ch=getchar();
	while(ch>='0'&&ch<='9') ans=ans*10+ch-'0',ch=getchar();
	if(last=='-') ans=-ans;
	return ans;
}

const int maxn=5e5+10;
const ll inf=-1e18+10;
int n;
ll k,d;
ll s[maxn],x[maxn];
ll l=0,r=0,mid=0;
ll f[maxn];
deque<ll>q;

bool check(ll mid)
{
	q.clear() ;
	for(int i=1;i<=n;i++) f[i]=inf;f[0]=0;
	ll lef=(d-mid<0?1:d-mid);
	ll rig=d+mid;
	int j=0;
	for(int i=1;i<=n;i++){
		while(x[i]-x[j]>rig) j++;
		while(x[i]-x[j]<=rig&&x[i]-x[j]>=lef&&j<i){
			while(!q.empty() &&(f[j]>=f[q.back() ]||x[i]-x[q.back()]>rig)) q.pop_back() ;
			q.push_back(j) ;
			j++;
		}
		while(!q.empty() &&x[i]-x[q.front() ]>rig) q.pop_front() ;
		if(!q.empty()) f[i]=f[q.front()]+s[i];
		if(f[i]>=k) return 1;
	}
	return 0;
}

int main()
{
	n=read();d=read();k=read();
	for(int i=1;i<=n;i++) x[i]=read(),s[i]=read();
    l=0,r=x[n];
    while(l<r){
    	mid=(l+r)>>1;
    	if(check(mid)) r=mid;
    	else l=mid+1;
	}
	if(!check(l)) {
		printf("-1\n");
		return 0;
	}
	printf("%lld\n",l);
	return 0;
}
posted @ 2020-01-05 16:57  晔子  阅读(273)  评论(0编辑  收藏  举报