#include using namespace std; const int N = 500010; typedef long long LL;//五年OI一场空,不开LONG LONG int n, d, k; // n:格子数,d:机器人可以跳的距离,k:想要取得的分值 int x[N], w[N]; // 距离数组x[i]表示i号格子距离出发点的距离,w[i]从i号格子中可以获得的分值 LL f[N]; // f[i]:到达i点时可以获取到的最大分值 int q[N]; // 单调队列,记录的是格子号 bool check(int g) { // g 描述的是给定的金币数量 LL res = 0; // 多次dp,每次需要清空统计数组 memset(f, -0x3f, sizeof f); // 预求最大,先设最小 f[0] = 0; // 递推起点数据,分值为0,这个要先看状态转移方程,再思考整体初始化、起点初始化 int hh = 0, tt = -1; // L,R : 左右边界 int L = max(1, d - g), R = d + g; /* ④ 为什么要先处理入队列,再处理出队列?反过来为什么是错的呢? 答:我们向单调队列中添加节点的逻辑是发现x[i]-x[k]>=L,也就是k没有进过队列,没有参加 评比过,并且,两个格子间的距离大于了L,就让它进来试试。 但是,这样就真的可以了吗?因为我们知道,单调队列中保存是一个个格子号,而这些格子号必须在i左侧指定的范围内才行。 x[i]-x[k]>=L是成立了,那会不会k离i太远了,超过了上限R呢?如果是这样的话,那么k是不能停留在队列中的,需要去除掉。 */ for (int i = 1, j = 0, k = 0; i <= n; i++) { // ① 新元素入队列 while (x[i] - x[k] >= L) { // i走的挺快,k有资格参评了 while (hh <= tt && f[q[tt]] <= f[k]) tt--; // 年龄比k大,值比k小的都去死 q[++tt] = k++; // k入队列 } // ② 越界元素出队列 while (x[i] - x[j] > R) j++; // j出界,j无法跳到i while (hh <= tt && q[hh] < j) hh++; // j都出界了,单调队列也需要维护,把比j小的都干掉 // ③ 队列非空,队列头中保存的就是前面[L,R]范围内的分数最大值所对应的y号格子 if (hh <= tt) f[i] = f[q[hh]] + w[i]; // 再加上i号格子中的w[i]分值,就是总分值 // 时刻更新最大值 res = max(res, f[i]); } // 因为分值中存在负数,一旦是在半途中出现可以获取到的分值大于等于k时,就可以停止掉,表示已经找到了一个金币数量满足增加了自由度后,可以取得k这样的分值 return res >= k; } int main() { // 加快读入 ios::sync_with_stdio(false), cin.tie(0); cin >> n >> d >> k; // 格子数量,机器人每次可以跳的距离,想要拿到的分数 for (int i = 1; i <= n; i++) cin >> x[i] >> w[i]; // 起点到第i个格子的距离,第i个格子的分数 int L = 0, R = 1e9, ans = -1; // 二分的左右无脑边界值 /* 二分的最后结果有两种方式: 1、使用ans变量记录最终结果,ans初始化为-1,每次找到check()通过的mid,ans=mid,这样,如果最终有答案ans就记录的是答案,否则就记录的是-1。 2、不使用ans变量记录,而是最后再用!check(L)然后输出-1。 个人认为方法1更易理解。 */ while (L < R) { int mid = L + R >> 1; if (check(mid)) { R = mid; ans = mid; } else L = mid + 1; } printf("%d\n", ans); return 0; }