洛谷 P6902 [ICPC 2014 WF] Surveillance 题解

Surveillance

link

词汇积累:surveillance n. 监视,监察 surveillance video 监控录像

Description

给定一个长度为 \(n\) 的环,有 \(k\) 个区域被覆盖,求最小的满足环被完全覆盖的区域数量。

保证 \(3 \le n \le 10^6\)\(1 \le k \le 10^6\)

Solution

我们熟知,链上的区间覆盖问题的一般做法是:对于当前线段的右端点,选择另外一条线段,跳到它的右端点,使得这条新线段的左端点不大于当前线段的右端点,并重复该步骤。

首先想到断环为链。具体地,对于线段 \([l,r]\)(顺时针方向):如果 \(l<r\) 则照常读入;如果 \(l > r\) 则将 \(r\) 右移 \(n\) 个单位长度,以达到囊括其覆盖区域的等效效果。

于是我们的目标变为:在长度为 \(2n\) 的一条新链上,找到能完全覆盖这个新链的每个长度为 \(n\) 的子链的最少线段数。

如果想要暴力搜索每一个长度为 \(n\) 的子链,复杂度 \(O(n^2)\),显然超时。

注意到,对于每个线段而言,从它出发,将要跳到的那条线段是固定的。因此,可以用一个表记录线段之间的跳跃关系。

又注意到这个表可以被倍增,即定义 \(nxt_{i,j}\) 表示以 \(i\) 为左端点的线段,往右跳 \(2^j\) 步后所能到达的线段右端点。因此复杂度变为 \(O(n \log n)\)

倍增实现的做法:对于每个起始位置 \(i\),维护从高到低遍历的二进制位 \(j\)。记当前跳到的线段左端点为 \(cur\)。明察二进制表,找到 \(cur\) 应当跳往的线段右端点位置 \(nxt_{cur,j}\)。每次遍历一个 \(j\),都将 \(2^j\) 的值纳入贡献。

思考:为什么 \(j\) 要从高到低遍历?因为二进制有一个性质,即对于某二进制数的第 \(i\) 位而言,如果它为 \(1\),那么前 \(i-1\) 位再怎么变化,都无法变化成比 \(2^i\) 更大的数。这样可以保证,遍历出的结果永远是被精确匹配的答案。

(不知道讲没讲明白,但我应该是理解了吧)

Code

#include <bits/stdc++.h>
#define int long long
#define inf 1e18
#define debug cout << '!';
#define filein(x) freopen(#x".in", "r", stdin);
#define fileout(x) freopen(#x".out", "w", stdout);
#define file(x) filein(x) fileout(x)
// #define Fast_IO
using namespace std;
#ifdef Fast_IO
inline int read() {
	int x = 0, f = 1; char c = getchar();
	while (c < '0' or c > '9') { if (c == '-') f = -1; c = getchar(); }
	while (c >= '0' and c <= '9') { x = x * 10 + c - '0'; c = getchar(); }
	return x * f;
}
void write(int x) {
	if (x < 0) putchar('-'), x = -x;
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0'); return;
}
#endif
const int MaxN = 2e6, MaxL = 20;
int n, k, ans = inf;
int nxt[MaxN + 5][MaxL + 5];
struct Segment {
	int l, r;
	friend bool operator < (Segment x, Segment y) {
		return x.l < y.l;
	}
} seg[MaxN + 5];
signed main() {
	cin.tie(0) -> sync_with_stdio(0);
	cin >> n >> k;
	for (int i = 1; i <= k; i++) {
		cin >> seg[i].l >> seg[i].r;
		if (seg[i].l > seg[i].r) seg[i].r += n;
	}
	sort(seg + 1, seg + 1 + k);
	// 对于每一个线段,找到应跳到的下一个线段的右端点
	int cur = 1, r = 0;
	for (int i = 1; i <= 2 * n; i++) {
		while (cur <= k and seg[cur].l <= i) {
			r = max(r, seg[cur].r + 1);
			cur++;
		}
		nxt[i][0] = r;
	}
	// 构建倍增表
	for (int j = 1; j <= MaxL; j++) {
		for (int i = 1; i <= 2 * n; i++) {
			nxt[i][j] = nxt[nxt[i][j - 1]][j - 1];
		}
	}
	// 二进制拆位倍增
	for (int i = 1; i <= 2 * n; i++) {
		int cur = i, res = 0;
		for (int j = MaxL; ~j; j--) {
			if (nxt[cur][j] - i < n) {
				cur = nxt[cur][j];
				res += (1 << j);
			}
		}
		cur = nxt[cur][0], res++;
		if (cur - i >= n) ans = min(ans, res);
	}
	if (ans == inf) cout << "impossible\n";
	else cout << ans << '\n';
	return 0;
}
posted @ 2026-05-20 16:24  L-Coding  阅读(1)  评论(0)    收藏  举报