洛谷 P6167 - [IOI2016]shortcut(单调队列+二分)
目前 ZLOJ 上总时限最大的题(
在下文中,称 \(x_i\) 表示 \(i\) 点在一维坐标轴上的坐标,这个可以前缀和求出。
碰到这类题,一个套路是二分答案。假设二分到 \(mid\),等价于检查是否存在一对点 \((p,q)\) 使得在 \(p,q\) 之间加入 \(c\) 的边后任意两点 \(i,j\) 之间的距离都 \(\le mid\)。考察 \(i,j(i>j)\) 点最短路的来源的可能性,有
- 不经过 shortcut:\(x_i-x_j+d_i+d_j\),如果这已经 \(\le k\) 就忽略这条边吧,反正它不影响 check 的合法性。
- 经过 shortcut:\(|x_i-x_p|+|x_j-x_q|+d_i+d_j+c\)。这里有个绝对值,考虑一个常见套路:\(|x|=\max(x,-x)\),于是我们可以将不等式 \(|x_i-x_p|+|x_j-x_q|+d_i+d_j+c\le k\) 拆成四个不等式:
- \((x_i-x_p)+(x_j-x_q)+d_i+d_j+c\le k\)
- \((x_i-x_p)+(x_q-x_j)+d_i+d_j+c\le k\)
- \((x_p-x_i)+(x_j-x_q)+d_i+d_j+c\le k\)
- \((x_p-x_i)+(x_q-x_j)+d_i+d_j+c\le k\)
移个项可以得到:
- \(x_p+x_q\ge (x_i+d_i)+(x_j+d_j)+c-k\)
- \(x_p-x_q\ge (x_i+d_i)-(x_j-d_j)+c-k\)
- \(x_p-x_q\le k-(x_i-d_i)-(x_j+d_j)-c\)
- \(x_p+x_q\le k-(x_i-d_i)-(x_j-d_j)-c\)
我们考虑枚举 \(i\),并在枚举 \(i\) 的同时一并维护 \(\max\limits_{x_i-x_j+d_i+d_j>k}x_j+d_j\) 和 \(\max\limits_{x_i-x_j+d_i+d_j>k}d_j-x_j\),这部分可以单调队列处理出来。这样我们可以解出 \(x_p+x_q\) 和 \(x_p-x_q\) 的范围,解出它们各自的范围后枚举 \(q\),two pointers 找出对应的 \(p\) 的范围然后判一下交集是否为空即可。
时间复杂度 \(n\log n\)。
非常类似的一道题:CF685C,CF 上评分不算高,可惜我去年还不会做(
const int MAXN = 1e6;
int n, c; ll x[MAXN + 5], d[MAXN + 5];
bool check(ll k) {
static int q[MAXN + 5]; int fr = 1, tl = 0;
ll mx1 = 1e18, mn1 = -1e18, mx2 = 1e18, mn2 = -1e18, A = -1e18, B = -1e18;
for (int i = 1; i <= n; i++) {
if (A + x[i] + d[i] > k) {
while (fr <= tl && d[q[fr]] - x[q[fr]] + x[i] + d[i] > k)
chkmax(B, d[q[fr]] + x[q[fr]]), ++fr;
chkmin(mx1, k + (x[i] - d[i]) - c - A);
chkmax(mn1, B + (x[i] + d[i]) - k + c);
chkmin(mx2, k + (x[i] - d[i]) - c - B);
chkmax(mn2, A + (x[i] + d[i]) - k + c);
}
chkmax(A, d[i] - x[i]);
while (fr <= tl && d[q[tl]] - x[q[tl]] <= d[i] - x[i]) --tl;
q[++tl] = i;
}
if (mn1 > mx1 || mn2 > mx2) return 0;
int l1 = 1, r1 = 1, l2 = n, r2 = n;
for (int i = 1; i <= n; i++) {
while (l1 <= n && x[l1] - x[i] < mn2) ++l1;
while (r1 <= n && x[r1] - x[i] <= mx2) ++r1; --r1;
while (l2 && x[l2] + x[i] >= mn1) --l2; ++l2;
while (r2 && x[r2] + x[i] > mx1) --r2;
if (max(l1, l2) <= min(r1, r2)) return 1;
}
return 0;
}
int main() {
scanf("%d%d", &n, &c);
for (int i = 2, v; i <= n; i++) scanf("%d", &v), x[i] = x[i - 1] + v; x[n + 1] = 1e18;
for (int i = 1; i <= n; i++) scanf("%lld", &d[i]);
ll l = 1, r = 1e18, p = 0;
while (l <= r) {
ll mid = l + r >> 1;
if (check(mid)) p = mid, r = mid - 1;
else l = mid + 1;
}
printf("%lld\n", p);
return 0;
}