Jump and Treasure
传送门
题意:
Tom最近在玩一款游戏,有n + 1个柱子在游戏中,从左到右,柱子的编号从0 - n, 第i个柱子的坐标x = i, 在\([n + 1, \infty]\)都有站点,Tom能够赢得游戏,如果跳到这些站点,玩家的起始坐标是0,只能从从左跳到右,坐标必须是递增的,只能跳到柱子上或者站点上,否则她将会失去比赛。另外,他的跳跃能力是受到限制的,并且每一次的跳跃最远距离不超过p, 除了0这根柱子,其他的柱子上都会有宝箱,第i根柱子有\(a_i\)的金币,当然也有陷阱,他将会失去\(\vert{a_i}\vert\), 这个游戏有n层,Tom在第i层如果是跳到柱子上必须是i的倍数(言外之意,如果不跳到柱子上可以直接在p的限制内随意距离跳跃),现在有q次询问,每一次有一个x, 询问在第x层赢得比赛能够获得的最大的金钱数量,如果不能赢得比赛输出Noob
思路:
对于每一层如果x > p那就是不可能的情况,其他的可以dp求出这一层的最优情况,dp[i]代表我到这个位置时我能够获得的最多贡献,dp[i]的注意方程肯定是dp[i] = max(dp[j]) + a[i]其中(j % x == 0 && j < i && i - j <= p)求这种连续滑动的最大最小值可以用单调队列,再来考虑具体的点,题目要求的是赢得比赛,那最后一个状态肯定在\([n + 1, \infty]\),n + 1是能够让最多的点能跳到这个点的点(贪心思想),所以最末的状态是n + 1, 开头在0位置,中间的位置都是x的倍数 \(<=n\), 单调队列头元素就是最值,所以维护好队列里面是单调递减的即可,具体的维护请看代码,q次查询\(n\), 倍数\(log(n)\), 中间又有单调队列的操作,单调队列是基于倍数的所以\(log(n)\), 所以最后的时间复杂度为\(O(n * logn * logn)\), 差点点超时哦!😏
总结:
滑动的区间最大最小值可以用单调队列来维护哦😊
点击查看代码
#include <bits/stdc++.h>
#define endl '\n'
#define IOS ios::sync_with_stdio(false);
using namespace std;
typedef long long ll;
const ll INF = 0x3f3f3f3f;
const ll MAXN = 1e6 + 10;
ll n, q, p;
struct Node
{
ll val, pos;
} a[MAXN];
ll dp[MAXN];
map<ll, ll> mp;
int main()
{
IOS; cin.tie(0), cout.tie(0);
cin >> n >> q >> p;
for (int i = 1; i <= n; ++i)
{
cin >> a[i].val;
a[i].pos = i;
}
while (q--)
{
ll x;
cin >> x;
if (mp.count(x))
{
cout << mp[x] << endl;
continue;
}
if (x > p)
{
cout << "Noob" << endl;
continue;
}
dp[0] = 0;
vector<ll> v;
v.push_back(0); //先把0位置放入到可以跳到的点
for (int i = x; i <= n; i += x)
{
v.push_back(i);
}
v.push_back(n + 1); //因为最后是跳到[n + 1, 无穷]才算成功,所以最后跳到的点要把这个区间里的一个数作为最终状态,显然所有的情况中n + 1这个状态是最优的,因为n + 1是离能跳过来最近的所以只要讨论最后跳到n + 1的状态即可
deque<ll> dq;
dq.push_back(0); //刚开始只有0位置
for (int i = 1; i < v.size(); ++i)
{
while (dq.size() && v[i] - dq.front() > p)
dq.pop_front();
dp[v[i]] = dp[dq.front()] + a[v[i]].val;
while (dq.size() && dp[dq.back()] < dp[v[i]])
dq.pop_back();
dq.push_back(v[i]);
}
mp[x] = dp[n + 1];
cout << mp[x] << endl;
}
return 0;
}