弹簧高跷

目录

目录

目录

题目

题目描述

输入

输出

样例输入

样例输出

解析

代码 1.0

再优化

代码 2.0

代码 3.0


题目

题目描述

在草场上有一条直线,直线上有若干个目标点。每个目标点都有一个分值和一个坐标。现在你可以选择其中任意一个目标点开始跳,只能沿一个方向跳,并且必须跳到另一个目标点。且每次跳的距离都不能少于上一次的距离。请问你能得到的最大分值是多少?

输入

第一行一个整数N(1<=N<=1000).接下来从第二行到第N+1行,每一行包含两个整数x(i)和p(i),每个整数在区间[0,1000000]之间。

输出

输出格式:最大能获得的分值。

样例输入

6 
5 6 
1 1 
10 5 
7 6 
4 8 
8 10

样例输出

25

解析

易证:此题为二维dp

由此,我们可以用一个二维数组 f[i][j] 表示从 j 跳到 i 的最大得分,我们可以由此得出状态转移方程f[i][j] = max(f[j][k] + a[i].point)

如果就这样做的,时间复杂度是 O(n^3)的,所以我们可以进一步思考优化:改变循环的顺序!!!

为了方便,我们先考虑顺序的 dp 法,可以肯定,一定有 k < j < i,此时我们将 j 提出来,那么就有

k \rightarrow 1 - j - 1

i \rightarrow j + 1 - n 

一个适当的优化(我们首先进行尝试)

代码 1.0

#include<cstdio>
#include<cstring>
#include<algorithm>
#define M 1000 + 5
#define reg register
using namespace std;

int n;
struct node{
    int x,p;
    bool operator < (const node &rhs) const{
            return x < rhs.x;
    }
}a[M];
int f[M][M],ans,h;

inline int max(int x,int y){
    return x > y ? x : y;
}

int main(){
    scanf("%d",&n);
    for (reg int i = 1;i <= n; ++ i)
        scanf("%d%d",&a[i].x,&a[i].p);
    sort(a + 1,a + 1 +n);
    for (reg int j = 1;j <= n; ++ j){
        f[j][j] = a[j].p;
        ans = max(ans,f[j][j]);
        for (reg int i = j + 1;i <= n; ++ i){
            for (reg int k = j;k >= 1; -- k){
                if (a[j].x - a[k].x <= a[i].x - a[j].x ){
                    f[i][j] = max(f[i][j],f[j][k] + a[i].p);
                    ans = max(ans,f[i][j]);
                }
            }
        }
    }
    for (reg int j = n;j >= 1; -- j){
        f[j][j] = a[j].p;
        for (reg int i = j - 1;i >= 1; -- i){
            for (reg int k = j;k <= n; ++ k){
                if (a[k].x - a[j].x <= a[j].x - a[i].x){
                    f[i][j] = max(f[i][j],f[j][k] + a[i].p);
                    ans = max(ans,f[i][j]);
                }
            }
        }
    }
    printf("%d\n",ans);
    return 0;
}
  1. 因为没有确定方向,所以要进行两次
  2. k 从 j 开始表明以 j 为起点

接着给一份WA证明:

再优化

既然不能AC,那就一定还存在优化方法,我们接着上文的方法继续研究,如果身为读者的你接下来会研究什么?我想,只剩下 i 与 k 了

我们进行一次假设吧,如果 i 越来越大,那么 k 呢?你会神奇地发现 k 的取值范围也变大了,强调一下,不是变小了,所以我们可以想到,上一次 k 的状态可以取到当前,所以我们用一个 sum 之类的变量存储一下, k 到 i 的最大得分就行了

代码 2.0

#include<cstdio>
#include<algorithm>
#define M 1000 + 5
#define reg register
using namespace std;

int n,ans;
struct node{
    int id,poi;
    bool operator < (const node &rhs) const {
        return id < rhs.id;
    }
}a[M];
int f[M][M];

inline int max(int x,int y){
    return x < y ? y : x;
}
int main(){
    scanf("%d",&n);
    for (reg int i = 1;i <= n; ++ i)
        scanf("%d%d",&a[i].id,&a[i].poi);
    sort(a + 1,a + 1 + n);//重载,所以不需要 cmp
    for (reg int j = 1;j <= n; ++ j){ //第一遍顺着来(k < j < i)
        f[j][j] = a[j].poi;//初值
        int sum = a[j].poi;//存 k 到 j 的最大得分,这儿是 j 到 j 的得分
        ans = max(sum,ans);
        int k = j - 1;
        for (reg int i = j + 1;i <= n; ++ i){
            sum += a[i].poi;
            while ( k && a[j].id - a[k].id <= a[i].id - a[j].id){//因为 i 与 j 的距离只会越来越远,所以 k 的取值范围会越来越广
                sum = max(sum,f[j][k] + a[i].poi);
                -- k;
            }
            f[i][j] = max(f[i][j],sum);
            ans = max(ans,f[i][j]);
            sum -= a[i].poi;
        }
    }
    for (reg int j = n;j >= 1; -- j){
        f[j][j] = a[j].poi;
        int sum = a[j].poi;
        int k = j + 1;
        for (reg int i = j - 1;i >= 1; -- i){
            sum += a[i].poi;
            while( k <= n && a[k].id - a[j].id <= a[j].id - a[i].id){
                sum = max(sum,f[j][k] + a[i].poi);
                ++ k;
            }
            f[i][j] = max(f[i][j],sum);
            ans = max(ans,f[i][j]);
            sum -= a[i].poi;
        }
    }
    printf("%d\n",ans);
    return 0;
}

然而 sum 一会又加 a[i].poi,一会又减,很麻烦(因为这儿的 sum 存的是 j 到 k 的最大得分),则我们可以改变一下思路,让 sum 存储一下 k 到 j 的最大得分不就行了吗

代码 3.0

#include<cstdio>
#include<algorithm>
#define M 1000 + 5
#define reg register
using namespace std;
 
int n,ans;
struct node{
    int id,poi;
    bool operator < (const node &rhs) const {
        return id < rhs.id;
    }
}a[M];
int f[M][M];
 
inline int max(int x,int y){
    return x < y ? y : x;
}
int main(){
    scanf("%d",&n);
    for (reg int i = 1;i <= n; ++ i)
        scanf("%d%d",&a[i].id,&a[i].poi);
    sort(a + 1,a + 1 + n);//重载,所以不需要 cmp
    for (reg int j = 1;j <= n; ++ j){ //第一遍顺着来(k < j < i)
        f[j][j] = a[j].poi;//初值
        int sum = a[j].poi;//存最大得分,这儿是 j 到 j 的得分
        ans = max(sum,ans);
        int k = j - 1;
        for (reg int i = j + 1;i <= n; ++ i){
            while ( k && a[j].id - a[k].id <= a[i].id - a[j].id){//因为 i 与 j 的距离只会越来越远,所以 k 的取值范围会越来越广
                sum = max(sum,f[j][k]);
                -- k;
            }
            f[i][j] = max(f[i][j],sum + a[i].poi);
            ans = max(ans,f[i][j]);
        }
    }
    for (reg int j = n;j >= 1; -- j){
        f[j][j] = a[j].poi;
        int sum = a[j].poi;
        int k = j + 1;
        for (reg int i = j - 1;i >= 1; -- i){
            while( k <= n && a[k].id - a[j].id <= a[j].id - a[i].id){
                sum = max(sum,f[j][k]);
                ++ k;
            }
            f[i][j] = max(f[i][j],sum + a[i].poi);
            ans = max(ans,f[i][j]);
        }
    }
    printf("%d\n",ans);
    return 0;
}
 

 

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值