CSP历年复赛题-P3957 [NOIP2017 普及组] 跳房子

原题链接:https://www.luogu.com.cn/problem/P3957

题意解读:有n个格子,每个格子有不同的距离和分数,从起点,每次可跳距离为d,用g金币后可跳距离范围可以变成max(d-g,1) ~ d+g,

求最小的g,使得可跳跃得分不少于k。

解题思路:

1、单调性分析:

如果g越大,可跳跃的范围就越大,理论上能得的分数越多,因此可以通过二分g,来判断是否能得到k分,如果可以则继续缩小g,如果不行则放大g。

2、如何判断能否至少得k分:

联想到最大子段和,但这里没有必要连续,可以认为是最大子序列的和

设每次跳跃距离范围l = max(d-g,1), r = d+g

设x[i]表示i的距离,s[i]表示i的分数

设f[i]表示以i结尾的最大子序列的和,

f[i] = max(f[j] + s[i]),j取i-1 ~ 1,并且j的距离要在合理范围内

什么是合理范围?

如果x[j] + l > x[i],就不合理,因为从j跳不到i

如果x[j] + r < x[i],也不合理,同样从j跳不到i

以上算法的复杂度为O(n^2*logn)

先尝试编写代码,得到部分分!

80分代码:

#include <bits/stdc++.h>
using namespace std;

const int N = 500005;

int n, d, k;
int x[N], s[N];
long long f[N];

bool check(int g)
{
    int l = max(d - g, 1), r = d + g;
    memset(f, -0x3f, sizeof(f));
    f[0] = 0;
    for(int i = 1; i <= n; i++)
    {
        for(int j = i - 1; j >= 0; j--)
        {
            if(x[j] + l > x[i]) continue; //i要从1开始,因为第一个点也不一定能到
            if(x[j] + r < x[i]) break;
            f[i] = max(f[i], f[j] + s[i]);
            if(f[i] >= k) return true;
        }
    }
    return false;
}

int main()
{
    cin >> n >> d >> k;
    for(int i = 1; i <= n; i++) cin >> x[i] >> s[i];
    int l = 1, r = 1e9, ans = -1;
    while(l <= r)
    {
        int mid = (l + r) >> 1;
        if(check(mid)) ans = mid, r = mid - 1;
        else l = mid + 1;
    }
    cout << ans;
    return 0;
}

如果数据增强,O(n^2*logn)理论上是无法通过的,需要进一步优化

3、单调队列优化

根据转移方程f[i] = max(f[j] + w[i])可知,i 只与一定范围内的j有关,且只和最大的f[j]有关,因此可以通过单调队列快速得到i之前有效范围内的最大f[j]

100分代码:

#include <bits/stdc++.h>
using namespace std;

const int N = 500005;

int n, d, k;
int x[N], s[N];
long long f[N];

bool check(int g)
{
    int l = max(d - g, 1), r = d + g;
    memset(f, -0x3f, sizeof(f));
    deque<int> q; //优先队列
    f[0] = 0;
    int j = 0; //j用来枚举i前面所有数,入队
    for(int i = 1; i <= n; i++)
    {
        //枚举所有j,去尾,入队
        while(x[j] + l <= x[i]) //j的位置+最小跳跃距离不能超过i
        {
            while(!q.empty() && f[q.back()] <= f[j]) q.pop_back();
            q.push_back(j);
            j++;
        }
        //去头
        while(!q.empty() && x[q.front()] + r < x[i]) q.pop_front();
        //计算
        if(!q.empty()) f[i] = f[q.front()] + s[i];

        if(f[i] >= k) return true;
    }
    return false;
}

int main()
{
    cin >> n >> d >> k;
    for(int i = 1; i <= n; i++) cin >> x[i] >> s[i];
    int l = 1, r = 1e9, ans = -1;
    while(l <= r)
    {
        int mid = (l + r) >> 1;
        if(check(mid)) ans = mid, r = mid - 1;
        else l = mid + 1;
    }
    cout << ans;
    return 0;
}

 

posted @ 2024-06-09 11:49  五月江城  阅读(69)  评论(0编辑  收藏  举报