洛谷 P6902 [ICPC 2014 WF] Surveillance 题解
Surveillance
词汇积累: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;
}

浙公网安备 33010602011771号