APIO 2012 守卫 | 差分 / 线段树 + 贪心

Posted Milky Way

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了APIO 2012 守卫 | 差分 / 线段树 + 贪心相关的知识,希望对你有一定的参考价值。

题目:luogu 3634

 

首先把为 0 的区间删去,重新标号,可以差分也可以线段树。

 

把包含其他线段的线段删去,原因 1 是它没有用,原因 2 下面再说。然后,贪心选取最少的点来满足所有线段,即选取还没有点在上面的线段的右端点。如下图中选取的红色方格。

倘若不删去包含其他线段的线段,如上图中的蓝色虚线,我们在贪心选取点的时候,就会先扫到蓝线的左端点而后扫到第二条红线,按照规则,我们会选择蓝线的右端点 6 号点,接下来扫到第二条红线时,由于它上面并没有点被选取,所以又会选取它的右端点 5 号点,这样显然不是最优的,这就是第 2 个原因。

 

选完点后,从左往右扫,每扫到一个选取的点,如果该点不是必选的(覆盖它的线段 l ≠ r),就尝试若不选它是否可行,即选取它左边相邻的点,计算出选取这个点时的最少点数,计算方法如下。

 

如上图,用 F[ i ] 表示前 i 条线段需要选取的最少点数,G[ i ] 表示后 i 条线段需要选取的最少点数,假设现在我们尝试不选 5 号点,计算选取 4 号点时需要选取的最少点数,然后与 k 比较,方法是二分找出右端点最大且小于 4 的线段 x,找出左端点最小且大于 4 的线段 y,若 F[ x ] + G[ y ] + 1 大于 k 则尝试失败,说明先前点是必选的。

 1 #include <cstdio>
 2 #include <string>
 3 #include <algorithm>
 4 
 5 const int N = 100005;
 6 
 7 struct line {
 8     int l, r, f;
 9     bool operator < (const line &cmp) const {
10         return l < cmp.l;
11     }
12 } a[N];
13 
14 int b[N], L[N], R[N], be[N], F[N], G[N];
15 
16 int read() {
17     int x = 0, f = 1;
18     char c = getchar();
19     while (!isdigit(c)) {
20         if (c == \'-\') f = -1;
21         c = getchar();
22     }
23     while (isdigit(c)) {
24         x = (x << 3) + (x << 1) + (c ^ 48);
25         c = getchar();
26     }
27     return x * f;
28 }
29 
30 int main() {
31     int n = read(), k = read(), m = read();
32     for (int i = 1; i <= m; ++ i) {        //差分标记为 0 的区间 
33         a[i].l = read(), a[i].r = read(), a[i].f = read();
34         if (a[i].f == 0) ++b[a[i].l], --b[a[i].r+1];
35     }
36     int cur = 0, cnt = 0;
37     for (int i = 1; i <= n; ++ i) {        //去除为 0 的区间并重新标号 
38         cur += b[i];
39         if (cur == 0) L[i] = R[i] = ++cnt, be[cnt] = i;
40     }
41     if (cnt == k) {                        //恰好满足 
42         for (int i = 1; i <= cnt; ++ i) printf("%d\\n", be[i]);
43         return 0;
44     }
45     L[n + 1] = n + 1;
46     for (int i = 1; i <= n; ++ i)
47         if (R[i] == 0) R[i] = R[i - 1];
48     for (int i = n; i >= 1; -- i)
49         if (L[i] == 0) L[i] = L[i + 1];
50     cnt = 0;
51     for (int i = 1; i <= m; ++ i) {
52         if (a[i].f == 0) continue;
53         int l = L[a[i].l], r = R[a[i].r];
54         if (l <= r) a[++cnt].l = l, a[cnt].r = r;
55     }
56     std::sort(a + 1, a + cnt + 1);
57     int top = 0;
58     for (int i = 1; i <= cnt; ++ i) {    //去除包含其他线段的线段 
59         while (top && a[i].l >= L[top] && a[i].r <= R[top]) --top;
60         L[++top] = a[i].l, R[top] = a[i].r;
61     }
62     int l = n + 1, r = 0;
63     for (int i = 1; i <= top; ++ i) {    //贪心选取最少的点 
64         if (L[i] > r) F[i] = F[i - 1] + 1, r = R[i];
65         else F[i] = F[i - 1];
66     }
67     for (int i = top; i >= 1; -- i) {
68         if (R[i] < l) G[i] = G[i + 1] + 1, l = L[i];
69         else G[i] = G[i + 1];
70     }
71     bool ok = 0;
72     for (int i = 1; i <= top; ++ i) {    //尝试不选 
73         if (F[i] == F[i - 1]) continue;
74         if (L[i] == R[i]) {
75             printf("%d\\n", be[R[i]]);
76             ok = 1; continue;
77         }
78         int l = 1, r = i - 1, x = 0, y = top + 1;
79         while (l <= r) {                //二分查找 
80             int mid = l + ((r - l) >> 1);
81             if (R[mid] < R[i] - 1) x = mid, l = mid + 1;
82             else r = mid - 1;
83         }
84         l = i + 1, r = top;
85         while (l <= r) {
86             int mid = l + ((r - l) >> 1);
87             if (L[mid] > R[i] - 1) y = mid, r = mid - 1;
88             else l = mid + 1;
89         }
90         if (F[x] + G[y] + 1 > k) {
91             printf("%d\\n", be[R[i]]);
92             ok = 1;
93         }
94     }
95     if (!ok) puts("-1");
96     return 0;
97 }

 

以上是关于APIO 2012 守卫 | 差分 / 线段树 + 贪心的主要内容,如果未能解决你的问题,请参考以下文章

P1552 [APIO2012]派遣

NOIP2012借教室[线段树|离线 差分 二分答案]

p3634 [APIO2012]守卫

BZOJ 2809: [Apio2012]dispatching [主席树 DFS序]

BZOJ2809: [Apio2012]dispatching

[APIO2018] New Home 新家 [线段树,multiset]