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;
}