1 条题解

  • 0
    @ 2026-5-19 20:18:57

    D. 按位悖论 详细题解(附标程)

    问题简述

    • 数组 a[1..n]a[1..n]b[1..n]b[1..n],常数 vv
    • 区间 [l,r][l,r]好区间(blbr)v(b_l|\dots|b_r) \ge v
    • 好区间的 优美值max(al,,ar)\max(a_l,\dots,a_r)
    • 操作:单点修改 bib_i;查询 [L,R][L,R] 内所有好区间的最小优美值。
    • 约束:n,q2105\sum n,\sum q\le 2\cdot10^5

    核心转化

    S=v1S = v-1。则条件 (blbr)v(b_l|\dots|b_r) \ge v 等价于 (blbr)>S(b_l|\dots|b_r) > S
    在二进制下,x>Sx > S 当且仅当存在一个二进制位 jj,使得 SS 的第 jj 位为 00xx 的第 jj 位为 11
    因此,好区间等价于:存在某个 jj 满足 (S>>j)&1=0(S>>j)\&1 = 0,且区间内至少有一个 bb 在该位为 11


    数据结构设计

    使用线段树,每个节点维护一个结构体 Info,包含:

    • pre[30]:区间内从左往右第一个使该位为 11 的位置(00 表示不存在)。
    • suf[30]:区间内从右往左第一个使该位为 11 的位置。
    • ans:该区间内所有好区间的最小优美值,若不存在则为 INF
    • l, r:节点区间的左右端点。

    叶子节点(位置 pp

    • bp>Sb_p > S(即 bpvb_p \ge v),则单点区间 [p,p][p,p] 是好区间,ans = a[p];否则 ans = INF
    • 对于每个位 jj,若 (bp>>j)&1=1(b_p>>j)\&1 = 1,则 pre[j]=suf[j]=p,否则为 00

    合并操作

    合并左节点 xx 和右节点 yyx.r+1=y.lx.r+1 = y.l)得到 zz

    1. 基础信息

      • z.l=x.lz.l = x.lz.r=y.rz.r = y.r
      • z.ans=min(x.ans,y.ans)z.ans = \min(x.ans, y.ans)
      • 对每个 jj
        • z.pre[j]=x.pre[j]?x.pre[j]:y.pre[j]z.pre[j] = x.pre[j] ? x.pre[j] : y.pre[j](左边有则用左边,否则用右边)。
        • z.suf[j]=y.suf[j]?y.suf[j]:x.suf[j]z.suf[j] = y.suf[j] ? y.suf[j] : x.suf[j](右边有则用右边,否则用左边)。
    2. 跨越区间的好区间
      任何跨越 x.rx.ry.ly.l 的区间形如 [u,v][u, v],其中 ux.ru \le x.rvy.lv \ge y.l。我们想找最小的 max(a[u..v])\max(a[u..v]),且存在某个 jj 满足 (S>>j)&1=0(S>>j)\&1 = 0 且区间内该位为 11
      采用贪心:从中间向两边扩展,按位从高到低处理。
      初始化左边界 pl=x.rpl = x.r,右边界 pr=y.lpr = y.l

      jj292900

      • 在左区间中找 ≤ pl 且第 jj 位为 11 的最右位置:u = x.suf[j],若存在且 uplu \le pl 则有效,否则视为无效(代码中用 u = min(u, pl) 并结合后续判断处理)。
      • 在右区间中找 ≥ pr 且第 jj 位为 11 的最左位置:v = y.pre[j],若存在且 vprv \ge pr 则有效,否则视为无效。
      • 计算两个候选区间的最大值(使用 RMQ):
        • lans = (u ? ds.qry(u, pr) : INF)
        • rans = (v ? ds.qry(pl, v) : INF)
      • 判断 (S>>j)&1(S>>j)\&1
        • 若为 1SS 的这一位是 11 → 这正是我们要找的“自由位”(因为 SS11 的位在 vv 中为 00)。只要区间包含该位,OR 就大于 SS,从而成为好区间。此时取 min(lans,rans)\min(lans, rans) 更新 z.ansz.ans,然后 立即退出循环(高位优先,第一个自由位就能给出最优解)。
        • 若为 0SS 的这一位是 00vv 的这一位是 11。该位单独出现不能使 OR > SS,但我们可以利用它来缩小边界:比较 lanslansransrans,选择较小的方向,将边界向内移动(pl = upr = v),然后继续循环。如果两边都没有有效位置(uv 均为 0),则循环自然结束(无法形成跨越的好区间)。

      循环结束时,z.ansz.ans 已经包含了可能的最佳跨越区间的最小优美值。

    3. 返回 zz


    辅助数据结构

    • RMQ:对 aa 数组建立稀疏表,支持 O(1)O(1) 查询区间最大值。因为 aa 从不修改。
    • 线段树:使用静态数组 seg[N<<2] 预分配节点。建树、更新、查询均为 O(logn30)O(\log n \cdot 30)

    查询处理

    对于查询 [L,R][L,R]

    • 全局变量 ansInfo 清空。
    • 在线段树中递归查询区间 [L,R][L,R],每遇到一个完全覆盖的节点,调用 reply(node.info) 将当前节点信息与 ansInfo 合并(ansInfo = ansInfo + node.info,注意顺序从左到右)。
    • 最终答案为 ansInfo.ans,若仍为 INF 则输出 1-1

    复杂度分析

    • 预处理 RMQ:O(nlogn)O(n\log n)
    • 建树:每个节点 O(30)O(30),总 O(n30)O(n\cdot 30)
    • 单次修改:O(logn30)O(\log n \cdot 30)
    • 单次查询:访问 O(logn)O(\log n) 个节点,每个节点合并 O(30)O(30),总 O(logn30)O(\log n \cdot 30)
    • 总复杂度:O((n+q)30logn)O((n+q)\cdot 30 \cdot \log n),在 2×1052\times10^5 范围内可行。

    标程代码

    #include <bits/stdc++.h>
    using namespace std;
    
    constexpr int N = 200000 + 5;   // 题目总和只有 2e5
    constexpr int V = 30;
    constexpr int INF = INT_MAX;
    constexpr int L = 19;
    
    int n, q, S;
    int a[N], b[N];
    
    struct RMQ {
        int st[L][N];
    
        void init() {
            for (int i = 1; i <= n; i++)
                st[0][i] = a[i];
    
            for (int k = 1; k < L; k++) {
                for (int i = 1; i + (1 << k) - 1 <= n; i++) {
                    st[k][i] = max(st[k - 1][i],
                                   st[k - 1][i + (1 << (k - 1))]);
                }
            }
        }
    
        inline int qry(int l, int r) {
            int k = __lg(r - l + 1);
            return max(st[k][l], st[k][r - (1 << k) + 1]);
        }
    } ds;
    
    struct Info {
        int pre[V];
        int suf[V];
    
        int ans;
        int l, r;
    
        Info() {
            memset(pre, 0, sizeof(pre));
            memset(suf, 0, sizeof(suf));
            ans = INF;
            l = r = 0;
        }
    
        inline void clear() {
            memset(pre, 0, sizeof(pre));
            memset(suf, 0, sizeof(suf));
            ans = INF;
            l = r = 0;
        }
    
        inline void upd(int p) {
            clear();
    
            l = r = p;
    
            if (b[p] > S)
                ans = a[p];
    
            for (int j = 0; j < V; j++) {
                if ((b[p] >> j) & 1) {
                    pre[j] = p;
                    suf[j] = p;
                }
            }
        }
    
        friend Info operator + (const Info &x, const Info &y) {
            if (!x.l) return y;
            if (!y.l) return x;
    
            Info z;
    
            z.l = x.l;
            z.r = y.r;
    
            z.ans = min(x.ans, y.ans);
    
            for (int i = 0; i < V; i++) {
                z.pre[i] = x.pre[i] ? x.pre[i] : y.pre[i];
                z.suf[i] = y.suf[i] ? y.suf[i] : x.suf[i];
            }
    
            int pl = x.r;
            int pr = y.l;
    
            for (int i = V - 1; i >= 0; i--) {
    
                int u = x.suf[i];
                int v = y.pre[i];
    
                if (u)
                    u = min(u, pl);
    
                if (v)
                    v = max(v, pr);
    
                int lans = u ? ds.qry(u, pr) : INF;
                int rans = v ? ds.qry(pl, v) : INF;
    
                if (lans < rans) {
                    if ((S >> i) & 1) {
                        if (lans < z.ans)
                            pl = u;
                        else
                            break;
                    } else {
                        z.ans = min(z.ans, lans);
                    }
                } else {
                    if ((S >> i) & 1) {
                        if (rans < z.ans)
                            pr = v;
                        else
                            break;
                    } else {
                        z.ans = min(z.ans, rans);
                    }
                }
            }
    
            return z;
        }
    };
    
    Info ansInfo;
    
    struct Node {
        int l, r, mid;
    
        Node *ls, *rs;
    
        Info info;
    };
    
    Node seg[N << 2];
    int tot;
    
    Node* build(int l, int r) {
    
        Node *u = &seg[++tot];
    
        u->l = l;
        u->r = r;
        u->mid = (l + r) >> 1;
    
        if (l == r) {
            u->info.upd(l);
            return u;
        }
    
        u->ls = build(l, u->mid);
        u->rs = build(u->mid + 1, r);
    
        u->info = u->ls->info + u->rs->info;
    
        return u;
    }
    
    inline void pushup(Node *u) {
        u->info = u->ls->info + u->rs->info;
    }
    
    void update(Node *u, int p) {
    
        if (u->l == u->r) {
            u->info.upd(p);
            return;
        }
    
        if (p <= u->mid)
            update(u->ls, p);
        else
            update(u->rs, p);
    
        pushup(u);
    }
    
    inline void reply(const Info &x) {
        ansInfo = ansInfo + x;
    }
    
    void query(Node *u, int l, int r) {
    
        if (u->l >= l && u->r <= r) {
            reply(u->info);
            return;
        }
    
        if (l <= u->mid)
            query(u->ls, l, r);
    
        if (r > u->mid)
            query(u->rs, l, r);
    }
    
    void solve() {
    
        cin >> n >> S;
        S--;
    
        for (int i = 1; i <= n; i++)
            cin >> a[i];
    
        ds.init();
    
        for (int i = 1; i <= n; i++)
            cin >> b[i];
    
        tot = 0;
        Node *rt = build(1, n);
    
        cin >> q;
    
        while (q--) {
    
            int op;
            cin >> op;
    
            if (op == 1) {
    
                int p, x;
                cin >> p >> x;
    
                b[p] = x;
    
                update(rt, p);
    
            } else {
    
                int l, r;
                cin >> l >> r;
    
                ansInfo.clear();
    
                query(rt, l, r);
    
                cout << (ansInfo.ans == INF ? -1 : ansInfo.ans) << ' ';
            }
        }
    
        cout << '\n';
    }
    
    int main() {
    
        ios::sync_with_stdio(false);
        cin.tie(nullptr);
    
        int T;
        cin >> T;
    
        while (T--) {
            solve();
        }
    
        return 0;
    }
    

    总结

    本题的核心是利用 S=v1S=v-1 将条件转化为“存在一个 SS 中为 00 的位在区间 OR 中为 11”,然后通过线段树维护每个位的最左最右出现位置,在合并时采用高位优先的贪心策略,逐步收缩边界并更新答案。标程实现了这一算法,可以高效处理在线修改和区间查询。

    • 1

    信息

    ID
    7264
    时间
    30000ms
    内存
    1024MiB
    难度
    10
    标签
    递交数
    1
    已通过
    1
    上传者