题目描述
跳房子,也叫跳飞机,是一种世界性的儿童游戏,也是中国民间传统的体育游戏之一。
跳房子的游戏规则如下:
在地面上确定一个起点,然后在起点右侧画 n 个格子,这些格子都在同一条直线上。每个格子内有一个数字(整数),表示到达这个 格子能得到的分数。玩家第一次从起点开始向右跳,跳到起点右侧的一个格子内。第二次再从当前位置继续向右跳,依此类推。规则规定:
玩家每次都必须跳到当前位置右侧的一个格子内。玩家可以在任意时刻结束游戏,获得的分数为曾经到达过的格子中的数字之和。
现在小 R 研发了一款弹跳机器人来参加这个游戏。但是这个机器人有一个非常严重的缺陷,它每次向右弹跳的距离只能为固定的 d。小 R 希望改进他的机器人,如果他花 g 个金币改进他的机器人,那么他的机器人灵活性就能增加 g,但是需要注意的是,每 次弹跳的距离至少为 11。具体而言,当 g<dg<d 时,他的机器人每次可以选择向右弹跳的距离为 d−g,d−g+1,d−g+2,…,d+g−1,d+gd−g,d−g+1,d−g+2,…,d+g−1,d+g;否则当 g≥dg≥d 时,他的机器人每次可以选择向右弹跳的距离为 1,2,3,…,d+g−1,d+g1,2,3,…,d+g−1,d+g。
现在小 R 希望获得至少 k 分,请问他至少要花多少金币来改造他的机器人。
输入格式
第一行三个正整数 n,d,k 分别表示格子的数目,改进前机器人弹跳的固定距离,以及希望至少获得的分数。相邻两个数 之间用一个空格隔开。
接下来 n 行,每行两个整数 xi,si 分别表示起点到第 i 个格子的距离以及第 i个格子的分数。两个数之间用一个空格隔开。保证 xi 按递增顺序输入。
输出格式
共一行,一个整数,表示至少要花多少金币来改造他的机器人。若无论如何他都无法获得至少 k 分,输出 −1。
输入格式
第一行三个正整数 n,d,k 分别表示格子的数目,改进前机器人弹跳的固定距离,以及希望至少获得的分数。相邻两个数 之间用一个空格隔开。
接下来 n 行,每行两个整数 xi,si 分别表示起点到第 i个格子的距离以及第 i个格子的分数。两个数之间用一个空格隔开。保证 xi 按递增顺序输入。
输出格式
共一行,一个整数,表示至少要花多少金币来改造他的机器人。若无论如何他都无法获得至少 k 分,输出 −1 。
输入输出样例
输入 #1复制
7 4 10 2 6 5 -3 10 3 11 -3 13 1 17 6 20 2
输出 #1复制
2
输入 #2复制
7 4 20 2 6 5 -3 10 3 11 -3 13 1 17 6 20 2
输出 #2复制
-1
说明/提示
样例 1 说明
花费 22 个金币改进后,小 R 的机器人依次选择的向右弹跳的距离分别为 2,3,5,3,4,32,3,5,3,4,3,先后到达的位置分别为 2,5,10,13,17,202,5,10,13,17,20,对应 1,2,3,5,6,71,2,3,5,6,7 这 66 个格子。这些格子中的数字之和 1515 即为小 R 获得的分数。
样例 2 说明
由于样例中 77 个格子组合的最大可能数字之和只有 1818,所以无论如何都无法获得 2020 分。
数据规模与约定
对于全部的数据满足 1≤n≤5001≤n≤500,1≤d≤2×1031≤d≤2×103,1≤xi,k≤1091≤xi,k≤109,∣si∣<105∣si∣<105。
---------------------------------------------------------------------------------------------------------------------------------
分析与解答:
A-G分别表示1号到7号格子,红色文字表示该格子对应的分值。

初始状态,当d = 4时,无法由起点跳到其它点,此时需要花费2金币改造,改造后机器人的移动范围变为[2,6],此时:
 1. 机器人跳到A点,得6分,总分6分
 2. 机器人跳到B点,得-3分,总分3分
 3. 机器人跳到C点,得3分,总分6分
 4. 机器人跳到E点,得1分,总分7分
 5. 机器人跳到F点,得6分,总分13分
所以,当花费2金币进行改造时,得分不低于10分。
解答1:
算法思想(二分搜索+动态规划)
 假设已求得了最优解,即花费g 
 个金币进行改造后,得分不低于k 
 。如果在此状态下再花费若干金币,那么得分一定不低于k 
 。因此花费的金币数和得分之间满足单调性质,是一个单调递增关系(不一定是严格单调递增),可以使用二分求解在花费不同金币进行改造时得分是否满足条件。
 要求花费g 
 个金币进行改造后的最高得分,可以使用动态规划的思想:
 状态表示: f[i]表示在花费g个金币进行改造后,跳过前i个格子得到的最高得分
 状态转移:f[i] = max{f[j] + w[i]},其中max(1,d−g)≤dist[i]−dist[j]≤d+g 
 时间复杂度 O(n×d×logxn) ,需要对DP进行剪枝。 
#include<iostream>
#include<queue>
#include<cstring>
#include<cmath>
using namespace std;
typedef long long ll;
int n,d,k;
int a[500010][2];
ll f[500010],sum=0;
bool check(int g) {
    cout<<"g="<<g<<' '<<endl;
    int l=max(1,d-g),r=d+g;
    cout<<"%%%"<<l<<' '<<r<<endl;
    memset(f,-127,sizeof f);
    f[0]=0;
    for(int i=1; i<=n; i++) {
        //每个格子
        int td;
        for(int j=0; j<i; j++) {
            //当前格子的前面格子,从小到大去寻找
            td=a[i][0]-a[j][0];
            cout<<"格子***"<<j<<" ,格子举例"<<td<<endl;
            if(td>=l&&td<=r) {
                // f[i]积分,当前i格子的分数
                f[i]=max(f[i],f[j]+a[i][1]);
            }
            if(f[i]>=k) return true;
            if(td<l) break;
        }
            cout<<i<<' '<<f[i]<<endl;
    }
    return false;
}
int main() {
    scanf("%d %d %d",&n,&d,&k);
    for(int i=1; i<=n; i++) {
        scanf("%d %d",&a[i][0],&a[i][1]);
        if(a[i][1]>0) {
            cout << "a[" << i << "][1]" << endl;
            sum += a[i][1];
            cout << "sum = " << sum << endl;
        }
    }
    if(sum<k) {
        //所有的正分加起来都不够希望获得的分数
        printf("-1");
        return 0;
    }
    //二分
    int l=0,r=max(0,a[n][0]-d);
    cout << "a[" << n-1 << "][0]=" << a[n][0] << endl;
    cout << "d = " << d << endl;
    while(l<r) {
        cout<<'*'<<l<<' '<<r<<endl;
        int mid=(l+r)/2;
        if(check(mid)) r=mid;
        else l=mid+1;
    }
    printf("%lld",l);
    return 0;
}解答2:基本思想
要解决这个问题,我们需要动态规划(Dynamic Programming,DP)的思想。我们要确定最少需要花费多少金币来改造机器人,使其能够获得至少 k 分。以下是详细的步骤和思路:
-  输入处理: - 读取 n,d,k。
- 读取每个格子的位置和分数。
 
- 读取 
-  确定DP状态: - dp[i][j]表示花费- j个金币时,到达第- i个格子能获得的最大分数。
 
-  状态转移: - 对于每个格子 i和每个花费j,我们需要尝试所有可能的跳跃距离,并更新dp[i][j]。
- 跳跃距离的范围根据 g和d的关系分为两种情况:- 当 g < d时,跳跃距离范围是[d-g, d+g]。
- 当 g >= d时,跳跃距离范围是[1, d+g]。
 
- 当 
 
- 对于每个格子 
-  初始化: - 起点位置特殊处理,dp[j]初始化为 0(起点没有分数,但是可以作为跳跃的出发点)。
 
- 起点位置特殊处理,
-  结果计算: - 遍历所有格子和所有花费,计算能得到的最大分数。
- 最终找到最小的花费 j使得在某个格子上的分数大于等于k。
 
-  边界情况处理: - 如果无法获得至少 k分,返回-1。
 
- 如果无法获得至少 
以下是实现该算法的C++代码:
#include <iostream>
#include <vector>
#include <algorithm>
#include <climits>
using namespace std;
struct Grid {
    int position;
    int score;
};
int main() {
    int n, d, k;
    cin >> n >> d >> k;
    
    vector<Grid> grids(n);
    for (int i = 0; i < n; ++i) {
        cin >> grids[i].position >> grids[i].score;
    }
    // 由于位置是按递增顺序输入的,我们可以直接使用下标来访问格子
    vector<vector<int>> dp(n + 1, vector<int>(n + 1, -1)); // n+1 个格子, 最多花费 n 个金币(松弛上界)
    dp = 0; // 在起点,不花费金币,得分为 0
    for (int g = 0; g <= n; ++g) { // 金币花费从 0 到 n
        for (int i = 0; i < n; ++i) { // 遍历每一个格子
            if (dp[i][g] == -1) continue; // 如果当前状态不可达,跳过
            int minJump = max(1, d - g);
            int maxJump = d + g;
            for (int jump = minJump; jump <= maxJump; ++jump) {
                // 查找下一个可达的格子
                for (int next = i + 1; next <= n; ++next) {
                    if (grids[next - 1].position - grids[i].position > jump) break;
                    if (grids[next - 1].position - grids[i].position == jump) {
                        dp[next][g] = max(dp[next][g], dp[i][g] + grids[next - 1].score);
                    }
                }
            }
        }
    }
    int minCoins = INT_MAX;
    for (int g = 0; g <= n; ++g) {
        for (int i = 0; i <= n; ++i) {
            if (dp[i][g] >= k) {
                minCoins = min(minCoins, g);
            }
        }
    }
    if (minCoins == INT_MAX) {
        cout << -1 << endl;
    } else {
        cout << minCoins << endl;
    }
    return 0;
}
解释
- 初始化:dp = 0,其他均为-1,因为初始时其他状态都是不可达的。
- 状态转移:通过遍历每一个格子和每一种花费,尝试所有可能的跳跃距离,更新 dp数组。
- 结果计算:找到最小的花费 g使得在某个格子上的分数大于等于k。
该算法的时间复杂度为 O(n3),在合理的数据范围内是可以接受的。
---------------------------------------------------------------------------------------------------------------------------------
解答3:
为了解决这个问题,我们需要考虑机器人在不同金币花费下的弹跳能力,并计算出在每种情况下能够获得的最大分数。我们的目标是找到最小的金币花费,使得获得的分数至少为 kk。
算法步骤
-  理解问题:机器人可以从起点向右跳,每次跳的距离为 d 或在花费一定金币后增加的灵活性范围内。目标是找到最小的金币花费,使得总分数至少为 k。 
-  预处理:首先,我们需要根据给定的格子位置和分数,构建一个数组或列表,其中每个元素代表一个格子的位置和分数。 
-  动态规划:使用动态规划来计算在不同金币花费下的最大分数。设 dp[g]表示花费 g 金币时可以获得的最大分数。我们需要初始化 dp[0]为在不花费金币时的最大分数,然后逐步增加金币花费,更新 dp 数组。 
-  更新 dpdp 数组:对于每个 g,我们需要考虑所有可能的弹跳距离,并计算在这些距离下可以获得的最大分数。这涉及到遍历所有格子,并尝试所有可能的弹跳组合。 
-  二分查找:由于我们需要找到最小的 g 使得 dp[g]≥kdp[g]≥k,我们可以使用二分查找来优化搜索过程。 
-  边界条件:确保在计算过程中考虑边界条件,例如当 g≥dg≥d 时,机器人可以跳任何距离。 
-  输出结果:如果找到了满足条件的最小 g,则输出这个值;如果没有这样的 g,则输出 −1。 
#include <iostream>
#include <vector>
#include <algorithm>
#include <unordered_map>
using namespace std;
int minGoldNeeded(int n, int d, int k, vector<pair<int, int>>& grid) {
    // 将格子按照位置排序
    sort(grid.begin(), grid.end());
    
    // 计算最大可能的金币花费
    int maxGold = 0;
    for (int i = 0; i < n; ++i) {
        maxGold = max(maxGold, grid[i].first);
    }
    
    // 使用一个哈希表来存储每个位置的最大分数
    unordered_map<int, int> dp;
    dp[0] = 0; // 0金币时的分数是0
    // 遍历每个格子,更新dp表
    for (int i = 0; i < n; ++i) {
        int pos = grid[i].first;
        int score = grid[i].second;
        for (int g = maxGold; g >= 0; --g) {
            if (dp.find(g) != dp.end()) { // 如果这个金币花费是有效的
                int newScore = dp[g] + score;
                int newGold = g + pos;
                if (newGold > maxGold) continue; // 如果新金币花费超过最大值,则跳过
                dp[newGold] = max(dp[newGold], newScore);
            }
        }
    }
    // 使用二分查找找到最小的金币花费
    int left = 0, right = maxGold;
    while (left < right) {
        int mid = left + (right - left + 1) / 2;
        if (dp.find(mid) != dp.end() && dp[mid] >= k) {
            right = mid - 1;
        } else {
            left = mid;
        }
    }
    // 如果找不到满足条件的金币花费,返回-1
    if (dp.find(left) == dp.end() || dp[left] < k) {
        return -1;
    }
    return left;
}
int main() {
    int n = 7, d = 4, k = 10;
    vector<pair<int, int>> grid = {{2, 6}, {5, -3}, {10, 3}, {11, -3}, {13, 1}, {17, 6}, {20, 2}};
    int result = minGoldNeeded(n, d, k, grid);
    cout << result << endl;
    return 0;
}

















