清零警报器

P4215 踩气球

维护一个长为 \(n\) 的序列 \(a_i\),支持 \(q\) 次操作,每次操作给定 \(x\),令 \(a_x \gets a_x-1\)。保证任意时刻 \(\forall i, a_i \ge 0\)

给定 \(m\) 个警报器,第 \(i\) 个警报器会在 \(\sum \limits_{k \in [l_i,r_i]}a_k=0\) 时报警。每次操作结束后,求出报警的次数。强制在线。

\(n,m,q \le 10^5\),1 秒,125 MB。

:::::success[题解]

对序列 \(a_i\) 建立线段树,维护区间和。

对于一个警报器 \([l_i,r_i]\),我们将其挂到线段树上的 \(O(\log n)\) 段节点。修改是对线段树单点修改。

对于被修改的节点,可以发现只有区间和清零时,我们才需要更新节点上挂载的警报器。这里,我们对每个警报器维护区间和,容易 \(O(1)\) 更新 & 判定是否报警。

由于区间和单调递减,每个警报器只会被其所在的节点暴力更新 \(1\) 次,拆成 \(\log\) 个节点。视 \(n,m,q\) 同阶总复杂度 \(O(n \log n)\)

:::::

:::::info[代码]

const int N = 1e5 + 5;
int n, m, q, ans, a[N]; 
i64 c[N], s[N];
void push(int x, i64 v) {
    mdebug(x, v);
    c[x] -= v;
    if (c[x] == 0) ans++;
}
struct segtree {
    #define ls (rt << 1)
    #define rs (rt << 1 | 1)
    i64 sum[N << 2];
    vector<int> to[N << 2];
    void pushup(int rt) {sum[rt] = sum[ls] + sum[rs];}
    void build(int l = 1, int r = n, int rt = 1) {
        if (l == r) return sum[rt] = a[l], void();
        int mid = (l + r) >> 1;
        build(l, mid, ls), build(mid + 1, r, rs), pushup(rt);
    }
    void modify(int tl, int tr, int c, int l = 1, int r = n, int rt = 1) {
        if (tl <= l && r <= tr) return to[rt].emplace_back(c), void();
        int mid = (l + r) >> 1;
        if (tl <= mid) modify(tl, tr, c, l, mid, ls);
        if (tr > mid) modify(tl, tr, c, mid + 1, r, rs);
    }
    void update(int x, int l = 1, int r = n, int rt = 1) {
        if (l == r) {
            if (--sum[rt] == 0) 
                for (int x : to[rt]) push(x, a[l]);
            return;
        }
        int mid = (l + r) >> 1;
        if (x <= mid) update(x, l, mid, ls);
        else update(x, mid + 1, r, rs);
        pushup(rt);
        if (sum[rt] == 0) 
            for (int x : to[rt]) push(x, s[r] - s[l-1]);
    }
} T;

void _main() {
    cin >> n >> m;
    for (int i = 1; i <= n; i++) cin >> a[i], s[i] = s[i - 1] + a[i];
    T.build();
    for (int i = 1, l, r; i <= m; i++) {
        cin >> l >> r; c[i] = s[r] - s[l - 1];
        T.modify(l, r, i);
    }
    cin >> q;
    for (int x; q--; ) {
        cin >> x; x = (x + ans - 1) % n + 1;
        T.update(x);
        cout << ans << '\n';
    }
}

:::::

P14241 [CCPC 2024 Shandong I] 传感器

P4215 的双倍经验。

P10787 [NOI2024] 树的定向

给定一棵 \(n\) 个节点的树,边有标号,再给定 \(m\) 条限制 \((a_i,b_i)\)

你需要构造一组定向方案,满足 \(\forall i \in [1,m], a_i \to b_i\) 不可达。再此基础上,最小化定向方案的字典序,并输出构造。数据保证有解。

\(n,m \le 5 \times 10^5\),3 秒,2048 MB。

::::::success[题解]

从 B 性质入手思考,容易得到 2-SAT 字典序最小解的转化,进而得到一般情形 k-SAT 的转化,从而问题不可做。

这启示我们从 A 性质入手思考。经过推演,可以发现有且仅有以下两种定向方案合法:

  • 奇数层向下,偶数层向上。
  • 奇数层向上,偶数层向上。

从而 A 性质的解法为,从两种方案中取出字典序较小的方案。

对于一般情形,相邻点对的限制相当于钦定了某些边的定向。显然,我们需要先对这些边定向。一部分边定向后,又会产生新的必须定向的边,这样得到一个迭代过程。同时,对于路径上已经存在反向边的限制,我们可以将其忽略。

现在的问题是,不存在必须定向的边时,如何进行定向。根据 A 性质的构造,我们得到本题的关键结论:

性质 若对剩余所有限制 \((a_i,b_i)\)\(a_i \to b_i\) 至少存在 \(2\) 条未定向边,则必然存在合法方案。

这是有解的充分不必要条件。这告诉我们,不存在必须定向的边时,可以直接钦定标号最小边的方向,重复这个流程。

下面复述一遍算法流程:

  • 维护队列 \(Q\),存放仅剩 \(1\) 条未定向边的所有限制。
  • 重复如下操作直到所有边被定向:
    • 取出队首限制。若其有效,则将路径上唯一未定向边定向。直到 \(Q\) 为空。
    • 将标号最小边定向,并将新的限制加入 \(Q\)

至此转化为数据结构问题。列出需要维护的操作:

  1. 给定 \(m\) 个报警器 \((a_i,b_i)\),当 \(a_i \to b_i\) 路径上未定向边数 \(\textcolor{red}{=1}\) 时报警。
  2. 在树上维护以下操作:
    • 给定 \(a_i,b_i\),判定 \(a_i \to b_i\) 是否存在反向边。
    • 给定 \(a_i,b_i\),找出 \(a_i \to b_i\) 未定向边的标号。
    • 给定 \(i\),将第 \(i\) 条边定向。

这两部分都存在 1log 做法,下面介绍思维难度较小的 2log 做法。

树剖拍到序列上。对于第一部分,可以使用清零警报器维护。具体地,我们对序列建立线段树,将警报器挂在 \(\log^2 n\) 个节点上。每次修改时,对于需要更新的线段树节点,当且仅当区间和 \(\le 1\) 时扫描挂载的警报器,判定其是否报警。由于每个警报器只会被 \(O(\log^2 n)\) 个节点检查 \(O(1)\) 次,总复杂度 \(O(m \log^2 n)\)

对于第二部分,仍转化到序列上。用一棵线段树维护 01 序列,区间查询存在性是简单的。求标号可以在线段树上二分,复杂度也是 2log。

\(n,m\) 同阶复杂度 \(O(n \log^2 n)\),实现优秀的话跑的飞快。具体实现细节见代码。

::::::

::::::info[代码]

const int N = 5e5 + 5;
int n, m, s[N], t[N], low[N], a[N], b[N], ans[N];
vector<pair<int, int>> e[N];

int dn, sz[N], fa[N], dep[N], son[N], dfn[N], top[N], seq[N], eid[N];
void dfs1(int u) {
    sz[u] = 1;
    for (auto [v, id] : e[u]) {
        if (v == fa[u]) continue;
        eid[v] = id, low[id] = v;
        fa[v] = u, dep[v] = dep[u] + 1, dfs1(v), sz[u] += sz[v];
        if (sz[v] > sz[son[u]]) son[u] = v;
    }
}
void dfs2(int u, int t) {
    dfn[u] = ++dn, top[u] = t, seq[dn] = u;
    if (son[u]) dfs2(son[u], t);
    for (auto [v, id] : e[u]) 
        if (v != fa[u] && v != son[u]) dfs2(v, v);
}
int lca(int u, int v) {
    while (top[u] != top[v]) {
        if (dep[top[u]] < dep[top[v]]) swap(u, v);
        u = fa[top[u]];
    } return dep[u] < dep[v] ? u : v;
}

void check(int x, int c);
// -1: undirected 0: up 1: down 
struct segtree {
    #define ls (rt << 1)
    #define rs (rt << 1 | 1)
    int cnt[N << 2];
    u8 has[N << 2];
    vector<int> to[N << 2];
    void pushup(int rt) {
        cnt[rt] = cnt[ls] + cnt[rs];
        has[rt] = has[ls] | has[rs];
    }
    void build(int l = 1, int r = n, int rt = 1) {
        if (l == r) return cnt[rt] = 1, void();
        int mid = (l + r) >> 1;
        build(l, mid, ls), build(mid + 1, r, rs), pushup(rt);
    }    
    bool exist(int tl, int tr, int c, int l = 1, int r = n, int rt = 1) {
        if (tl <= l && r <= tr) return has[rt] >> c & 1;
        int mid = (l + r) >> 1;
        if (tl <= mid && exist(tl, tr, c, l, mid, ls)) return true;
        if (tr > mid && exist(tl, tr, c, mid + 1, r, rs)) return true;
        return false;
    }
    int search(int tl, int tr, int l = 1, int r = n, int rt = 1) {
        if (!cnt[rt]) return -1;
        if (r < tl || l > tr) return -1;
        if (l == r) return l;
        int mid = (l + r) >> 1, tmp = -1;
        tmp = search(tl, tr, l, mid, ls);
        if (tmp != -1) return tmp;
        return search(tl, tr, mid + 1, r, rs);
    }
    void update(int x, int c, int l = 1, int r = n, int rt = 1) {
        if (l == r) {
            cnt[rt]--;
            for (int x : to[rt]) check(x, 1);
            return has[rt] |= 1 << c, void();
        }
        int mid = (l + r) >> 1;
        if (x <= mid) update(x, c, l, mid, ls);
        else update(x, c, mid + 1, r, rs);
        pushup(rt);
        if (cnt[rt] == 1)
            for (int x : to[rt]) check(x, r - l);
        if (cnt[rt] == 0)
            for (int x : to[rt]) check(x, 1);
    }
    void modify(int tl, int tr, int c, int l = 1, int r = n, int rt = 1) {
        if (tl <= l && r <= tr) return to[rt].emplace_back(c), void();
        int mid = (l + r) >> 1;
        if (tl <= mid) modify(tl, tr, c, l, mid, ls);
        if (tr > mid) modify(tl, tr, c, mid + 1, r, rs);
    }
} T;

int hd=1, tl=0, q[40*N], cnt[N];
bool del[N];

void push(int u, int v, int to) {
    cnt[to] = dep[u] + dep[v] - 2 * dep[lca(u, v)];
    while (top[u] != top[v]) {
        if (dep[top[u]] < dep[top[v]]) swap(u, v);
        T.modify(dfn[top[u]], dfn[u], to);
        u = fa[top[u]];
    }
    if (dfn[u] > dfn[v]) swap(u, v);
    if (dfn[u] < dfn[v]) T.modify(dfn[u]+1, dfn[v], to);
}

bool removed(int id) {
    if (del[id]) return true;
    int u = a[id], v = b[id], x = lca(u, v);
    while (top[u] != top[x]) {
        if (T.exist(dfn[top[u]], dfn[u], 1)) return del[id] = true;
        u = fa[top[u]];
    }
    if (u != x && T.exist(dfn[x]+1, dfn[u], 1)) return del[id] = true;
    while (top[v] != top[x]) {
        if (T.exist(dfn[top[v]], dfn[v], 0)) return del[id] = true;
        v = fa[top[v]];
    }
    if (v != x && T.exist(dfn[x]+1, dfn[v], 0)) return del[id] = true;
    return false;
}

void check(int x, int c) {
    cnt[x] -= c;
    if (cnt[x] == 1 && !removed(x)) q[++tl] = x;
} 
void direct(int i, int st) {
    ans[i] = low[i] == s[i] ? st : (st ^ 1);
    T.update(dfn[low[i]], st);
}

pair<int, int> search(int id) {
    int u = a[id], v = b[id], x = lca(u, v);
    while (top[u] != top[x]) {
        int r = T.search(dfn[top[u]], dfn[u]);
        if (r != -1) return {eid[seq[r]], 1};
        u = fa[top[u]];
    }
    if (u != x) {
        int r = T.search(dfn[x]+1, dfn[u]);
        if (r != -1) return {eid[seq[r]], 1};
    }
    while (top[v] != top[x]) {
        int r = T.search(dfn[top[v]], dfn[v]);
        if (r != -1) return {eid[seq[r]], 0};
        v = fa[top[v]];
    }
    if (v != x) {
        int r = T.search(dfn[x]+1, dfn[v]);
        if (r != -1) return {eid[seq[r]], 0};
    }
    return {-1, -1};
}

void _main() {
    read(n, n, m);
    for (int i = 1; i < n; i++) {
        read(s[i], t[i]);
        e[s[i]].emplace_back(t[i], i);
        e[t[i]].emplace_back(s[i], i);
    }
    dfs1(1), dfs2(1, 1), T.build();
    for (int i = 1; i <= m; i++) read(a[i], b[i]), push(a[i], b[i], i);
    for (int i = 1; i <= m; i++) check(i, 0);
    fill(ans + 1, ans + n, -1);
    for (int i = 1; i < n; ) {
        while (hd <= tl) {
            int x = q[hd++];
            if (removed(x)) continue;
            auto [i, st] = search(x);
            del[x] = true, direct(i, st);
        }
        while (i < n && ans[i] != -1) i++;
        if (i >= n) break;
        direct(i, low[i] == t[i]);
    }
    for (int i = 1; i < n; i++) write(ans[i]);   
} 

::::::

posted @ 2026-08-01 17:30  stripe_python  阅读(0)  评论(0)    收藏  举报