[BZOJ2811][Apio2012]Guard(贪心)

Address

https://www.lydsy.com/JudgeOnline/problem.php?id=2811

Solution

先假设忍者的数目不一定为恰好 K K 个。
考虑先把不可能有忍者(形如 lr0 的报告)的区间删掉。
然后求出至少有多少个忍者。
这是一个经典贪心:一些区间,选出最少的点,使每个区间包含至少一个点。
先将包含有其他区间的区间删掉,然后把区间按照左端点递增排序(由于包含有其他区间的区间已经被删掉,所以左端点排序后右端点也是递增的)。
如果至少有 K K 个忍者则直接输出这些忍者的位置。
我们可以求出:
f[i] 表示前 i i 个区间至少有多少个忍者;
g[i] 表示从 i i 个区间到最后一个区间至少有多少个忍者。
上面的那个经典贪心做完之后,我们会得出一组方案,满足所有给定区间里至少有一个忍者(但总数不一定恰好为 K ),且得出这组方案中每个忍者的位置。
显然,如果一个位置 i i 一定有忍者,那么 i 一定是上面的算法得出的方案中忍者的位置之一。
由贪心的过程易得 i i 一定是某一区间的末尾,并且如果
位置 i1 放忍者时,总忍者数一定超过 K K i i 一定有忍者。
我们可以利用二分查找,得出:
右端点小于 i1 的最后一个区间,为第 x x 个区间;
左端点大于 i1 的第一个区间,为第 y y 个区间。
如果 f[x]+g[y]+1>K ,那么位置 i i <script type="math/tex" id="MathJax-Element-1385">i</script> 一定有忍者。

Code

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define For(i, a, b) for (i = a; i <= b; i++)
#define Rof(i, a, b) for (i = a; i >= b; i--)
using namespace std;
inline int read() {
    int res = 0; bool bo = 0; char c;
    while (((c = getchar()) < '0' || c > '9') && c != '-');
    if (c == '-') bo = 1; else res = c - 48;
    while ((c = getchar()) >= '0' && c <= '9')
        res = (res << 3) + (res << 1) + (c - 48);
    return bo ? ~res + 1 : res;
}
const int N = 1e5 + 5;
int n, K, tm, m, T, cnt[N], f[N], g[N], id[N], rev[N], le[N], ri[N], tot;
struct cyx {int l, r;} q[N], stk[N];
bool comp(cyx a, cyx b) {return a.l < b.l || (a.l == b.l && a.r > b.r);}
int main() {
    int i, a, b, c, pos = 0; bool flag = 0;
    n = read(); K = read(); tm = read(); For (i, 1, tm) {
        a = read(); b = read(); c = read();
        if (!c) cnt[a]++, cnt[b + 1]--; else q[++m] = (cyx) {a, b};
    }
    For (i, 1, n) cnt[i] += cnt[i - 1]; For (i, 1, n) if (!cnt[i])
        rev[id[++T] = i] = T; if (T == K) {
        For (i, 1, T) printf("%d\n", id[i]); return 0;
    }
    For (i, 1, n) {if (!cnt[i]) pos = i; le[i] = pos;}
    pos = n + 1; Rof (i, n, 1) {if (!cnt[i]) pos = i; ri[i] = pos;}
    For (i, 1, m) q[i].l = rev[ri[q[i].l]], q[i].r = rev[le[q[i].r]];
    sort(q + 1, q + m + 1, comp); For (i, 1, m) {
        while (tot && q[i].r <= stk[tot].r) tot--; stk[++tot] = q[i];
    }
    int orzcyx = 0, orzpyz = T + 1; For (i, 1, tot)
        f[i] = stk[i].l > orzcyx ? (orzcyx = stk[i].r, f[i - 1] + 1) : f[i - 1];
    Rof (i, tot, 1)
        g[i] = stk[i].r < orzpyz ? (orzpyz = stk[i].l, g[i + 1] + 1) : g[i + 1];
    For (i, 1, tot) {
        if (f[i] == f[i - 1]) continue; if (stk[i].l == stk[i].r) {
            printf("%d\n", (flag = 1, id[stk[i].r])); continue;
        }
        int x = stk[i].r - 1, L = 1, R = tot; while (L <= R) {
            int mid = L + R >> 1; if (stk[mid].r < x) L = mid + 1;
            else R = mid - 1;
        }
        int tmp = R; L = 1; R = tot; while (L <= R) {
            int mid = L + R >> 1; if (stk[mid].l > x) R = mid - 1;
            else L = mid + 1;
        }
        if (f[tmp] + g[L] + 1 > K) printf("%d\n", (flag = 1, id[stk[i].r]));
    }
    if (!flag) puts("-1"); return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值