原題連結: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;
}