浏览器标题切换
浏览器标题切换end
把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

[luogu 3957]跳房子

题目链接

50分做法

挺显然的一个做法,因为金币量是单调的(如果你花i枚金币可以得到最优解,i+1枚也一定可以),所以可以二分答案

然后对于二分出来的每个答案,都做一遍dp,效率$O(n^2logn)$

#include <cstdio>
#include <cstring>
using namespace std;
#define N 500100
inline int read(){
    int x=0,f=1;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')f=-f;c=getchar();}
    while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+c-'0';c=getchar();}
    return x*f;
}
int n,d,k,a[N],s[N],dp[N];
int max(int x,int y){return x>y?x:y;}
bool check(int g){
    memset(dp,128,sizeof(dp));
    int t1=(d-g)?d-g:1,t2=d+g,mx=0;
    dp[0]=0;
    for(int i=1;i<=n;i++){
        for(int j=0;j<i;j++){
            if(a[i]-a[j]>=t1&&a[i]-a[j]<=t2)
                dp[i]=max(dp[i],dp[j]+s[i]);
        }
    }
    for(int i=1;i<=n;i++)mx=max(dp[i],mx);
    if(mx>=k)return 1;
    return 0;
}
int main(){
    n=read();d=read();k=read();
    for(int i=1;i<=n;i++)
        a[i]=read(),s[i]=read();
    int ans=0,l=0,r=10000000;
    while(l<=r){
        int mid=(l+r)>>1;
        if(check(mid))r=mid-1,ans=mid;
        else l=mid+1;
    }
    if(ans)printf("%d\n",ans);
    else puts("-1");
    return 0;
}
50分做法

100分做法

考虑怎么让效率降下来

50分的思路没问题,尝试一下能不能让每次dp的效率降下来

观察到答案其实也是单调的,dp[i]的答案是从前面i-d-g个数转移过来的,所以可以使用单调队列优化

总复杂度就变成$O(nlogn)$,能过100分的数据了

#include <cstdio>
#include <cstring>
#define ll long long
inline ll read(){
    ll x=0,f=1;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')f=-f;c=getchar();}
    while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+c-'0';c=getchar();}
    return x*f;
}
using namespace std;
#define inf (1<<30)
ll n,d,k,a[500010],s[500010];
ll dp[500010];
ll q[10000000];
ll max(ll x,ll y){return x>y?x:y;}
bool check(ll g){
    ll l=1,r=0,p=0,t1=max(d-g,1),t2=d+g;
    q[1]=0;
    for(int i=1;i<=n;i++){
        dp[i]=-inf;
        while(a[i]-a[p]>=t1&&p<i){
            while(l<=r&&dp[p]>=dp[q[r]])r--;
            q[++r]=p++;
        }
        while(a[i]-a[q[l]]>t2&&l<=r)l++;
        if(l>r||dp[q[l]]==-inf)continue;
        dp[i]=dp[q[l]]+s[i];
        if(dp[i]>=k)return 1;
    }
    return 0;
}
int main(){
    n=read(),d=read(),k=read();
    for(int i=1;i<=n;i++)
        a[i]=read(),s[i]=read();
    a[0]=0,s[0]=0;
    ll l=0,r=1000000000,ans=-1;
    while(l<=r){
        ll mid=(l+r)>>1;
        if(check(mid))ans=mid,r=mid-1;
        else l=mid+1;
    }
    printf("%lld\n",ans);
    return 0;
}
100分做法

 

posted @ 2018-08-01 09:59  henry_y  阅读(531)  评论(0编辑  收藏  举报