颓了一天整整六个小时颓出来的题,写篇颓废纪念一下
是,的,没,错,这道题花了 一整天。
好了不废话了,直接给出题目链接
如果想用更狠的官方数据来测,可以进这个OJ
老实说,蒟蒻感觉这题目有些绕……
一上来看到数据范围我吓呆了, ……
考虑二分最少所花的金币 。
左边界显然为1,右边界是 与最后一个点的距离 - 中的较大值。
其实直接搞成那啥十万也可以。
我们显然要用一个 函数检查 枚金币够不够,也就意味着我们要检查从起点能跳的最大距离是否大于等于 。
显然是个动态规划了。
在这里,设 表示格子 的距离, 表示格子 的值, 表示从格子 出发能取到的最大值。
只要学过DP应该都会转移方程吧……
{ }
起点能取到的最大值可以理解为 。
我如何颓掉六个小时的呢?
注意看看数据范围……
这个算法时间复杂度是 的,还要乘个二分的常数(20左右),面对五十万的数据范围,我不知道说什么好……
然而!
题目数据太良心。常数其实只有十几,格子之间并没有隔得很近,于是 几乎变成了 。
开始我没发现啊qwq,做完了看题解发现的。
如果你就此止步,注意开long long
,不然会出锅的。
于是初级的AC代码(十分钟就写完了qwq,被坑了六个小时):
#include <cstdio>
#include <queue>
#include <algorithm>
#define int long long
using namespace std;
int a[1000005], s[1000005], f[1000005], n, d, k;
priority_queue<int> q;
inline bool check(int g) {
f[n] = s[n];
for (int i = n - 1; i >= 0; i--) {
f[i] = s[i];
for (int j = i + 1; a[i] + d + g >= a[j] && j <= n; j++)
if (a[i] + d - g <= a[j] && f[j] + s[i] > f[i]) {
f[i] = f[j] + s[i];
}
}
return f[0] >= k;
}
signed main() {
scanf("%lld%lld%lld", &n, &d, &k);
for (int i = 1; i <= n; i++) scanf("%lld%lld", a + i, s + i);
int l = 1, r = 100005, mid, ans = r;
while (l <= r) {
mid = (l + r) / 2;
if (check(mid))
ans = mid, r = mid - 1;
else
l = mid + 1;
}
if (check(100005))
printf("%lld", ans);
else
puts("-1");
}
这应该就是普及组的初衷了,但是谁知道等我初一(我是个xxs)的时候普及组会不会提高难度呢。
于是我的优化是通过OI界著名数据结构神器单调队列实现的。
进阶思路:
显然我们就是要找到“位置满足要求的元素”中分数最大的。而且这个“位置满足要求的元素”在不断变化。单调队列就专门干这事儿。
为了叙述方便,提前 deque<int>q
作为单调队列,deque<int>dq
作为辅助队列。
这个队列中存放的是“可能被使用的元素”的编号。
什么是“可能被使用的元素”?
我们可以这样想,对于当前的格子 ,如果队列中的元素 比机器人下一个格子(i-1) 能跳的最近距离大,同时有一个元素 ,且 (即 ), ,那么我们需要保留元素 吗?既然元素 对于下一个格子来说有着足够的距离,那么也就意味着 在被“淘汰”(距离太远)前永远不可能成为满足要求的元素中的最大值,此时 就不是“可能被使用的元素”,应该删除。
上面一段话一定要看懂,这是单调队列的基本思想。看不懂可以结合下面的代码,如果实在看不懂,那就暂时不要学习单调队列。
整个队列删除完后,元素编号应该是递增的,每个元素能取到的最大值也是递增的。所以就直接给原来的DP降维了。
将队列的删除(维护)操作封装到一个函数defend
中就是:
inline void defend()
{
int p = -1;//能取到的最大值至少大于-1,不然对于其它元素来说没有利用价值,还不如不取
int t = q.size();//保存当前队列的元素个数
while (t --)
{
if (f[q.front()] > p)//如果这个元素的值比编号在他之前的格子的值都大,那么他是有保留价值的
q.push_back(q.front()), p = f[q.front()];//更新p,将新的有保留价值元素插到队尾。
q.pop_front();//弹出队首元素
}
}
这里有一个小问题,就是如果一个元素距离太近,怎么办?显然这个元素不能说是没有保留价值的,那么我们的dq
队列就是来存这些元素的。当遇到一个新的格子时,只要从队尾开始依次检查是否能使用队中元素就可以了。然后在将dq
队列中当前有用的元素都抖出来后,维护一遍队列就可以了。当然dq
队列的元素编号是从左往右递增的。
代码如下:
inline bool check(int g)
{
q.clear(); dq.clear();
for (int i = n; i >= 0; i --)
{
bool flag = 0;
while (q.size() && a[q.back()] > a[i] + d + g) q.pop_back();//距离太远的元素不能使用
while (dq.size() && a[dq.back()] >= a[i] + d - g)//如果dq的队尾元素可以使用了
{
if (a[dq.back()] <= a[i] + d + g && f[dq.back()] >= 0)//并且距离在限制范围内,值大于0
q.push_front(dq.back());//就把他插入到队列中
dq.pop_back();//不管有没有用都要删除dq队尾元素,因为他已经不是距离太近的元素了
flag = 1;
}
if (flag) defend();//维护一遍队列
f[i] = s[i];
if (q.size()) f[i] += f[q.back()];//加上队尾元素能取到的最大值
if (a[i - 1] + d - g <= a[i]) q.push_front(i);//如果这个元素距离合法
else dq.push_front(i);//如果这个元素距离太近,插到dq中
defend();//维护一遍队列
}
return f[0] >= k;
}
就这个单调队列,我写了五个多小时……
哎,自己还是太菜鸡了。毕竟只在一周前,在滑动窗口中接触过单调队列。
附上完整代码:
#include <cstdio>
#include <deque>
#define int long long
using namespace std;
int a[1000005], s[1000005], f[1000005], n, d, k;
deque<int> q;
deque<int> dq;
inline void defend()
{
int p = -1;
int t = q.size();
while (t --)
{
if (f[q.front()] > p)
q.push_back(q.front()), p = f[q.front()];
q.pop_front();
}
}
inline bool check(int g)
{
q.clear(); dq.clear();
for (int i = n; i >= 0; i --)
{
bool flag = 0;
while (q.size() && a[q.back()] > a[i] + d + g) q.pop_back();
while (dq.size() && a[dq.back()] >= a[i] + d - g)
{
if (a[dq.back()] <= a[i] + d + g && f[dq.back()] >= 0)
q.push_front(dq.back());
dq.pop_back();
flag = 1;
}
if (flag) defend();
f[i] = s[i];
if (q.size()) f[i] += f[q.back()];
if (a[i - 1] + d - g <= a[i]) q.push_front(i);
else dq.push_front(i);
defend();
}
return f[0] >= k;
}
signed main()
{
scanf("%lld%lld%lld", &n, &d, &k);
for (int i = 1; i <= n; i ++)
scanf("%lld%lld", a + i, s + i);
int l = 1, r, rl;
if (a[n] > d) r = rl = a[n];
else r = rl = d;
int mid, ans = r;
while (l <= r)
{
mid = (l + r) / 2;
if (check(mid)) ans = mid, r = mid - 1;
else l = mid + 1;
}
if (check(rl)) printf("%lld", ans);
else puts("-1");//如果是无解的qaq
}
另外由于官方数据太水(一开始就没有想考单调队列),所以单调队列耗时和朴素DP耗时差不多。。。