解题报告:P8348 环路运输

题意

给定一个有 nn 个点的环(1n1×1061 \le n \le 1 \times 10 ^ 6),环上每个点有一个权值 viv_i,环上每条边的长度均为 11,求 max1i,jn{vi+vj+dis(i,j)}\max \limits_{1 \le i, j \le n} \lbrace v_i + v_j + dis(i, j) \rbrace

思路

首先断环为链,将原序列复制一次到原序列后边

我们设 fif_i 表示 max1jn{vi+vj+dis(i,j)}\max \limits_{1 \le j \le n} \lbrace v_i + v_j + dis(i, j) \rbrace

由于是一个环,dis(i,j)dis(i, j) 表示的是 i,ji, j 之间的最短距离,也就是说 dis(i,j)=min{ij,n(ij)}dis(i, j) = \min \lbrace |i - j|, |n - (i - j)| \rbrace

所以一定有 dis(i,j)n2dis(i, j) \le \left\lfloor \frac{n}{2} \right\rfloor

为了绕过这个限制,我们可以钦定 fif_i(i,j)(i, j)顺时针距离不超过 n2\left\lfloor \frac{n}{2} \right\rfloor

在这个含意下,由于我们已经将原序列复制了一次,并不会对答案造成影响(感性理解一下)

那么新的含意下, fi=maxin2j<i{vi+vj+ij}f_i = \max \limits_{i - \left\lfloor \frac{n}{2} \right\rfloor \le j < i} \lbrace v_i + v_j + i - j\rbrace

那么考虑如何 O(1)O(1) 求出 fif_i

首先我们可以对 fif_i 进行如下变换:

fi=maxin2j<i{vi+vj+ij}=maxin2j<i{vjj}+vi+i\begin{aligned} f_i & = \max \limits_{i - \left\lfloor \frac{n}{2} \right\rfloor \le j < i} \lbrace v_i + v_j + i - j\rbrace \\ & = \max \limits_{i - \left\lfloor \frac{n}{2} \right\rfloor \le j < i} \lbrace v_j - j\rbrace + v_i + i \\ \end{aligned}

那么现在问题就变成了如何 O(1)O(1) 快速求出 maxin2j<i{vjj}\max \limits_{i - \left\lfloor \frac{n}{2} \right\rfloor \le j < i} \lbrace v_j - j \rbrace

显然,可以贡献到 fif_ijj 是不断变化的,但是一直有 in2j<ii - \left\lfloor \frac{n}{2} \right\rfloor \le j < i

这个区间可以看作是一个滑动窗口,那么我们要求的即为滑动窗口中的最小值

我们维护可以一个单调(双端)队列,队列中存的值为 viiv_i - i,且从头到尾单调递减

我们从头到尾遍历序列中的每个位置,进行如下操作:

  1. 把队列头部所有不满足 in2j<ii - \left\lfloor \frac{n}{2} \right\rfloor \le j < ijj 弹出队列
  2. 使用队首更新答案
  3. 将所有在队尾且小于 viiv_i - iin2j<ii - \left\lfloor \frac{n}{2} \right\rfloor \le j < ijj 弹出队列
  4. viiv_i - i 插入队尾

这样我们就求出了滑动窗口最值,同时也就求出了答案

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <bits/stdc++.h>
namespace solution
{
typedef long long LL;
const int MAXN = 2e6 + 5;
int n;
LL ans = 0;
LL v[MAXN];
std::deque<std::pair<LL, int>> q;
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; i++)
{
scanf("%lld", &v[i]);
v[i + n] = v[i];
}
for (int i = 1; i <= 2 * n; i++)
{
while (!q.empty() && q.front().second < i - n / 2)
{
q.pop_front();
}
if (!q.empty())
{
ans = std::max(ans, v[i] + q.front().first + i);
}
while (!q.empty() && (q.back().first < v[i] - i || q.back().second < i - n / 2))
{
q.pop_back();
}
q.push_back(std::make_pair(v[i] - i, i));
}
printf("%lld\n", ans);
return 0;
}
}
int main()
{
solution::main();
return 0;
}