好题笔记
跳房子
这一题是一可以说是二分答案的一个经典应用,想来讲一下不加优化的版本,也就是可以的一半的分。首先我们看到了最少的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;
}