TIOJ 1430:[APIO '12] 衛兵問題

TIOJ 1430:[APIO '12] 衛兵問題


題目大意:有 $n$ 的點,其中 $k$ 個點藏有忍者,有 $m$ 個衛兵,每個衛兵守衛區間 $[l, r]$,若 $[l, r]$ 裡有忍者,那 $c=1$,否則 $c=0$,請你輸出一定有忍者的點。

解法:首先可以先把必為 $0$ 的點去掉,然後觀察到若一個大區間覆蓋了一個小區間,那其實可以不考慮大區間,因為小區間必有 $1$,大區間也一定會有 $1$,我們就可以得到左界遞增,右界遞增的線段,如果不必為 $0$ 的點恰好有 $k$ 個,那這時可以直接輸出答案。如果我們想算一個點是不是一定會被用到,那可以計算它沒被用到的話可不可行,可以採用一個貪心策略,維護 $lp_i, rp_i$,$lp_i$ 代表前 $i$ 個線段最少要有幾個點才能符合條件,$rp_i$ 代表第 $i\sim m$ 個線段最少要有幾個點才能符合條件。每次看一個線段,若 $l=r$,那代表一定會用到那一點,否則一個線段只有當 $lp_i>lp_{i-1}$ 時的 $r$ 可以當候選人,若要計算沒有 $r$ 時合不合法,可以令用到的是 $r$ 左邊那一點,二分搜出在 $L_{r-1}$ 前且最右邊的線段編號 $pl$,與在 $L_{r-1}$ 後且最左邊的線段編號 $pr$,若 $lp_{pl}+1+rp_{pr}>k$,那代表此點一定要被選到。

$\text{Code:}$

#pragma GCC optimize("O3")
#pragma GCC target("popcnt")
#include <bits/stdc++.h>
using namespace std;

#define ll long long
#define Waimai ios::sync_with_stdio(false),cin.tie(0)
#define FOR(x,a,b) for(int x=a;x<=b;x++)
#define pb emplace_back
#define F first
#define S second

const int SIZE = 1e5 + 5;

int n, m, k, siz;
pair<int, int> p[SIZE];
int d[SIZE], L[SIZE], R[SIZE], lp[SIZE], rp[SIZE];

void solve() {
    cin >> n >> k >> m;
    while (m--) {
        int l, r, c;
        cin >> l >> r >> c;
        if (c == 0) {
            d[l]++;
            d[r + 1]--;
        } else {
            p[++siz] = {l, r};
        }
    }
    m = siz;
    R[n + 1] = n + 1;
    int cnt = 0;
    FOR (i, 1, n) {
        d[i] += d[i - 1];
        if (d[i]) L[i] = L[i - 1];
        else {
            cnt++;
            L[i] = R[i] = i;
        }
    }
    for (int i = n; i >= 1; i--) if (d[i]) R[i] = R[i + 1];
    if (cnt == k) {
        FOR (i, 1, n) if (d[i] == 0) cout << i << '\n';
        return;
    }
    FOR (i, 1, m) p[i] = {R[p[i].F], L[p[i].S]};
    sort (p + 1, p + m + 1, [] (auto p1, auto p2) {
        return p1.S != p2.S ? p1.S < p2.S : p1.F > p2.F;
    });
    siz = 1;
    FOR (i, 2, m) if (p[i].F > p[siz].F) p[++siz] = p[i];
    m = siz, cnt = 0;
    for (int i = 1, mx = 0; i <= m; i++) {
        lp[i] = lp[i - 1];
        if (p[i].F > mx) {
            lp[i]++;
            mx = p[i].S;
        }
    }
    for (int i = m, mn = n + 1; i >= 1; i--) {
        rp[i] = rp[i + 1];
        if (p[i].S < mn) {
            rp[i]++;
            mn = p[i].F;
        }
    }
    FOR (i, 1, m) if (lp[i] > lp[i - 1]) {
        auto [l, r] = p[i];
        if (l == r) {
            cnt++;
            cout << r << '\n';
            continue;
        }
        r = L[r - 1];
        int pl = lower_bound (p + 1, p + i, r, [] (pair<int, int> x, int r) {
            return x.S < r;
        }) - p - 1;
        int pr = lower_bound (p + i + 1, p + m + 1, pair<int, int> (r, n + 1)) - p;
        if (lp[pl] + rp[pr] + 1 > k) {
            cnt++;
            cout << p[i].S << '\n';
        }
    }
    if (cnt == 0) cout << "-1\n";
}

int main() {
    Waimai;
    solve();
}

我的分享就到這裡結束了,如果喜歡我的 $\text{Blog}$,歡迎追蹤!

留言

熱門文章