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;
}