洛谷P3957跳房子

8 篇文章 0 订阅
2 篇文章 0 订阅

题目链接

这是一道很有(du )趣(liu )的题目

洛谷P3957跳房子

题目思路

二分+深搜

我们可以发现分数是根据灵活性的上升而上升的,也就是说分数对于灵活性而言是有序的。

那么我们就完全没有必要去一个个枚举分数,而是可以用二分来加快速度。

然后我们用深搜dfs非常暴力地计算最大分数,并与k比较,就可以获得TLE20分

代码如下:

#include<iostream>
#define MAXN 500005
#define INF 0x3f3f3f3f
using namespace std;
int d,n,k,minl,maxl,ans;
struct node{
    int x,s;
}a[MAXN];
void dfs(int pos,int sum){
    ans=max(ans,sum);
    for(int i=pos+1;i<=n;i++){
        if(a[i].x-a[pos].x>maxl) break;
        if(a[i].x-a[pos].x<minl) continue;
        dfs(i,sum+a[i].s);
    }
}
bool check(int mid){
    minl=max(1,d-mid);
    maxl=d+mid;
    ans=-INF;
    dfs(0,0);
    return ans>=k;
}
int main(){
    cin>>n>>d>>k;
    for(int i=1;i<=n;i++){
        cin>>a[i].x>>a[i].s;
    }
    int l=0,r=INF;
    while(l<r){
        int mid=(l+r)/2;
        if(check(mid)) r=mid;
        else l=mid+1;
    }
    if(check(l)) cout<<l;
    else cout<<-1;
    return 0;
}

二分+动态规划

那么我们就需要思考加速的方法。很明显二分已经足够快了,所以导致我们TLE的罪魁祸首就是深搜。

那我们check函数里用什么呢? 当然是动态规划啦!

我们可以发现深搜中的前后关系是线性的,所以可以用动态规划。

只要中间过程大于等于k即可,因为后面的值一定越来越大。这样做,你可以获得TLE50分。

代码如下:

#include<iostream>
#define MAXN 500005
#define INF 0x3f3f3f3f
using namespace std;
int d,n,k,minl,maxl,ans;
struct node{
    int x,s;
}a[MAXN];
int f[MAXN];
bool check(int mid){
    minl=max(1,d-mid);
    maxl=d+mid;
    for(int i=1;i<=n;i++){
        f[i]=-INF;
        for(int j=0;j<i;j++){
            int dis=a[i].x-a[j].x;
            if(dis<minl) break;
            if(dis>maxl) continue;
            f[i]=max(f[i],f[j]+a[i].s);
        }
        if(f[i]>=k) return true;
    }
    return false;
}
int main(){
    cin>>n>>d>>k;
    for(int i=1;i<=n;i++){
        cin>>a[i].x>>a[i].s;
    }
    int l=0,r=INF;
    while(l<r){
        int mid=(l+r)/2;
        if(check(mid)) r=mid;
        else l=mid+1;
    }
    if(check(l)) cout<<l;
    else cout<<-1;
    return 0;
}

二分+动态规划+玄学

我们已经用动态规划获得了50分,但还是挂了。

怎么办?俗话说:“世上无难题,只要肯放弃。”

动规挂了,我们就优化动规

在一波玄学优化后……

#include<iostream>
#define MAXN 500005
#define INF 0x3f3f3f3f
using namespace std;
int d,n,k,minl,maxl,ans;
struct node{
    int x,s;
}a[MAXN];
int f[MAXN];
bool check(int mid){
    minl=max(1,d-mid);
    maxl=d+mid;
    for(int i=1;i<=n;i++){
        f[i]=-INF;
        for(int j=i-1;j>=0;j--){
            int dis=a[i].x-a[j].x;
            if(dis<minl) continue;
            if(dis>maxl) break;
            f[i]=max(f[i],f[j]+a[i].s);
        }
        if(f[i]>=k) return true;
    }
    return false;
}
int main(){
    cin>>n>>d>>k;
    for(int i=1;i<=n;i++){
        cin>>a[i].x>>a[i].s;
    }
    int l=0,r=INF;
    while(l<r){
        int mid=(l+r)/2;
        if(check(mid)) r=mid;
        else l=mid+1;
    }
    if(check(l)) cout<<l;
    else cout<<-1;
    return 0;
}

总之就是很玄学。。。

二分+动态规划+单调队列优化

我们发现,在check函数中我们使用了双层循环,其实是用不着的。

第二重循环中,我们遍历了很多无效的项,这时候,我们就可以通过单调队列优化来解决。

因为如果 a [ i ] . x − a [ j ] . x > m a x l a[i].x-a[j].x>maxl a[i].xa[j].x>maxl的话, a [ i + 1 ] . x − a [ j ] . x > m a x l a[i+1].x-a[j].x>maxl a[i+1].xa[j].x>maxl,这样 j j j这个点后面就彻底没用了。

所以这有用吗? 要不还是洗洗睡吧。

当然有用!

我们就可以弄个队列,对于点i,有用的时候就把它留在队列里,没用就把它踢出队列,扔进垃圾桶

这样, i i i这个点我们后面就不需要无效遍历了

具体代码如下

#include<iostream>
#define MAXN 500005
#define INF 0x3f3f3f3f
using namespace std;
int d,n,k,minl,maxl,ans,h,t;
struct node{
    int x,s;
}a[MAXN];
int f[MAXN],q[MAXN];
void push(int i){
    while(t>h&&f[q[t-1]]<f[i])
        t--;
    q[t]=i;
    t++;
}
bool check(int mid){
    minl=max(1,d-mid);
    maxl=d+mid;
    int j=0;
    h=t=0;
    for(int i=1;i<=n;i++){
        f[i]=-INF;
        while(j<i&&a[i].x-a[j].x>=minl) push(j++);
        while(h<t&&a[i].x-a[q[h]].x>maxl) h++;
        if(t==h||f[q[h]]==-INF) continue;
        else f[i]=f[q[h]]+a[i].s;
        if(f[i]>=k) return true; 
    }
    return false;
}
int main(){
    cin>>n>>d>>k;
    for(int i=1;i<=n;i++){
        cin>>a[i].x>>a[i].s;
    }
    int l=0,r=INF;
    while(l<r){
        int mid=(l+r)/2;
        if(check(mid)) r=mid;
        else l=mid+1;
    }
    if(check(l)) cout<<l;
    else cout<<-1;
    return 0;
}

完结撒花
这是我之前写在博客园上面的,现在放到CSDN博客上面来。

写在最后

感谢各位花了这么长的时间来看一个初二牲蒟蒻的博客。
我的博客

  • 5
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值