洛谷 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;
}
posted @ 2022-07-07 17:43  tzc_wk  阅读(37)  评论(0编辑  收藏  举报