题意
给定一个有 n 个点的环(1≤n≤1×106),环上每个点有一个权值 vi,环上每条边的长度均为 1,求 1≤i,j≤nmax{vi+vj+dis(i,j)}
思路
首先断环为链,将原序列复制一次到原序列后边
我们设 fi 表示 1≤j≤nmax{vi+vj+dis(i,j)}
由于是一个环,dis(i,j) 表示的是 i,j 之间的最短距离,也就是说 dis(i,j)=min{∣i−j∣,∣n−(i−j)∣}
所以一定有 dis(i,j)≤⌊2n⌋
为了绕过这个限制,我们可以钦定 fi 中 (i,j) 的顺时针距离不超过 ⌊2n⌋
在这个含意下,由于我们已经将原序列复制了一次,并不会对答案造成影响(感性理解一下)
那么新的含意下, fi=i−⌊2n⌋≤j<imax{vi+vj+i−j}
那么考虑如何 O(1) 求出 fi
首先我们可以对 fi 进行如下变换:
fi=i−⌊2n⌋≤j<imax{vi+vj+i−j}=i−⌊2n⌋≤j<imax{vj−j}+vi+i
那么现在问题就变成了如何 O(1) 快速求出 i−⌊2n⌋≤j<imax{vj−j}
显然,可以贡献到 fi 的 j 是不断变化的,但是一直有 i−⌊2n⌋≤j<i
这个区间可以看作是一个滑动窗口,那么我们要求的即为滑动窗口中的最小值
我们维护可以一个单调(双端)队列,队列中存的值为 vi−i,且从头到尾单调递减
我们从头到尾遍历序列中的每个位置,进行如下操作:
- 把队列头部所有不满足 i−⌊2n⌋≤j<i 的 j 弹出队列
- 使用队首更新答案
- 将所有在队尾且小于 vi−i 或 i−⌊2n⌋≤j<i 的 j 弹出队列
- 将 vi−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; }
|