NOIP2017普及组复赛——T4跳房子

题目描述

跳房子,也叫跳飞机,是一种世界性的儿童游戏,也是中国民间传统的体育游戏之一。

跳房子的游戏规则如下:

在地面上确定一个起点,然后在起点右侧画n个格子,这些格子都在同一条直线上。

每个格子内有一个数字(整数),表示到达这个格子能得到的分数。

玩家第一次从起点开始向右跳,跳到起点右侧的一个格子内。

第二次再从当前位置继续向右跳,依此类推。

规则规定:玩家每次都必须跳到当前位置右侧的一个格子内。

玩家可以在任意时刻结束游戏,获得的分数为曾经到达过的格子中的数字之和。

现在小R研发了一款弹跳机器人来参加这个游戏。

但是这个机器人有一个非常严重的缺陷,它每次向右弹跳的距离只能为固定的d。

小R希望改进他的机器人,如果他花g个金币改进他的机器人,那么他的机器人灵活性就能增加g,但是需要注意的是,每次弹跳的距离至少为1。

具体而言,当g<d时,他的机器人每次可以选择向右弹跳的距离为d-g, d-g+1, d-g+2,…,d+g-2,d+g-1,d+g;否则(当g≥d时),他的机器人每次可以选择向右弹跳的距离为1,2,3,…,d+g-2,d+g-1,d+g。

现在小R希望获得至少k分,请问他至少要花多少金币来改造他的机器人。

输入格式
第一行三个正整数n,d,k,分别表示格子的数目,改进前机器人弹跳的固定距离,以及希望至少获得的分数,相邻两个数之间用一个空格隔开。

接下来n行,每行两个正整数 x i , s i x_i,s_i xi,si,分别表示起点到第 i i i个格子的距离以及第 i i i个格子的分数。

两个数之间用一个空格隔开,保证 x i x_i xi按递增顺序输入。

输出格式
共一行,一个整数,表示至少要花多少金币来改造他的机器人。

若无论如何他都无法获得至少k分,输出-1。

样例

7 4 10
2 6
5 -3
10 3
11 -3
13 1
17 6
20 2

A-G分别表示1号到7号格子,红色文字表示该格子对应的分值。

在这里插入图片描述

初始状态,当d = 4时,无法由起点跳到其它点,此时需要花费2金币改造,改造后机器人的移动范围变为 [ 2 , 6 ] [2,6] [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 g g个金币进行改造后,得分不低于 k k k。如果在此状态下再花费若干金币,那么得分一定不低于 k k k。因此花费的金币数和得分之间满足单调性质,是一个单调递增关系(不一定是严格单调递增),可以使用二分求解在花费不同金币进行改造时得分是否满足条件。
  2. 要求花费 g g g个金币进行改造后的最高得分,可以使用动态规划的思想:
    • 状态表示: f[i]表示在花费g个金币进行改造后,跳过前i个格子得到的最高得分
    • 状态转移:f[i] = max{f[j] + w[i]},其中 max ⁡ ( 1 , d − g ) ≤ d i s t [ i ] − d i s t [ j ] ≤ d + g \max(1, d-g)\le dist[i]-dist[j]\le d+g max(1,dg)dist[i]dist[j]d+g

时间复杂度

O ( n × d × l o g x n ) O(n\times d\times log\frac{x}{n}) O(n×d×lognx),需要对DP进行剪枝。

代码实现

#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;
typedef long long LL;
const int N = 500010;
int n, d, k;
//五年OI一场空,不开long long见祖宗
LL dist[N], w[N], f[N];

//检查花费g个金币进行改造后,最高得分是否会超过k
bool check(int gold)
{
    
    
    //机器人能够弹跳的范围[L,R]
    int L = max(1, d - gold);
    int R = d + gold;
    
    //注意:这里要初始化为负无穷
    memset(f, 0xc0, sizeof f);
    f[0] = 0;
    
    for(int i = 1; i <= n; i ++)
    {
    
    
        //从i的前一个格子开始,枚举到起点
        for(int j = i - 1; j >= 0; j --)
        {
    
    
            //剪枝,机器人从j号格子加上R还是法到达i号格子
            //那么从j号格子之前的格子也无法到达i号格子
            if(dist[j] + R < dist[i]) break;
            
            //机器人从j号格子加上L还是超过了i号格子
            //那么继续尝试j号之前的格子
            if(dist[j] + L > dist[i]) continue;
            
            //机器人从j号格子可以转移到i号格子
            f[i] = max(f[i], f[j] + w[i]);
            
            if(f[i] >= k) return true;
        }
    }
    
    return false;
}

int main()
{
    
    
    scanf("%d%d%d", &n, &d, &k);

    for(int i = 1; i <= n; i ++)
        scanf("%lld%lld", &dist[i], &w[i]);
    
    //R的大小可以通过x/n来确定,平均距离为2000
    int L = 0, R = 20000, ans = -1;
    
    while(L < R)
    {
    
    
        //所有满足条件的情况都在mid的右边区间,
        //搜索右边区间最小值
        //适用二分搜索模板1
        int mid = L + R >> 1;
        
        if(check(mid))
        {
    
    
            ans = mid;
            R = mid;
        }
        else L = mid + 1;
    }
    
    cout << ans << endl;
    
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qiaoxinwei/article/details/108576675