[CFgym103260K] Rectangle Painting

题目链接 Rectangle Painting

\(n\) 个集合 \(S_i\),两种操作,

  1. 1 x l r\(S_l\)\(S_r\) 插入 \(x\)
  2. 2 l r 询问 \(\max\limits_{i=l}^r \{\text{mex}(S_i)\}\)

强制在线。

\(1\le n,l,r\le 2\times 10^5,1\le q\le 10^5,\texttt{10s,1024MiB}\)

upd on 2026/02/23:以前的题解比较乱,重新写了一遍。

解题思路

考虑线段树上打懒标记是比较困难的,因为更新懒标记后,子树信息是不好维护的。

考虑是否存在一个做法,可以不更新子树信息。先假设对每种颜色,涂色操作是不交的。这可以使用 ODT 实现。

然后肯定是要记录子树 \(\text{mex}\) 信息的,但是如果直接用 \(a_i=\max(a_{ls},a_{rs})\) 是不好修改的,所以考虑把 \(\text{mex}\) 的限制拆开。

考虑 \(\text{mex}\) 是最小的没出现的颜色。考虑把这个限制放在叶子到根的路径上,记为 \(b_i\),则信息更新就是 \(a_i=\min(\min_{j\in b_i}j,\max(a_{ls},a_{rs}))\)

考虑给节点分类,\(s_c=0,1,2\) 分别表示节点子树内的元素,没有被涂过 \(c\),一部分被涂过,全被涂过。

这里 \(s_c\) 不一定表示真实情况,但是一定要保证 \(s_c=1\) 是真实的。而 \(s_c=0\) 可能是标记未下传。

这样,我们让 \(c\) 挂在 \(s_c(x)=0,s_c(fa_x)=1\)\(b_x\) 上,就可以保证限制正确表示。

因为 \(s_c(fa_x)=1\) 意味着这个位置的标记正确下传了,但是 \(s_c(x)=0\) 意味着 \(x\) 子树真的没有被覆盖过。

考虑更新。如果当前更新到了节点 \(x\)

  • 如果全覆盖,则 \(s_c(x)=2\),更新 \(b_x\)
  • 否则
    • \(c\in b_x\),就要把标记往下扔。删除 \(b_x\) 中的 \(c\),记录变量 \(g=1\),并把变量递归下去。
    • 否则直接递归下去。
  • 递归下去之后,回溯之前,如果 \(g=1\) 意味着当前节点的子树是空白的,且 \(s_c(x)=1\),所以要更新子节点的标记。

分析一下复杂度。一次操作会新增 \(O(\log n)\) 个标记,使用 set 维护 \(b_x\),所以复杂度 \(O(\log^2 n)\)

总时间复杂度为 \(O(n\log^2 n)\)

record

10s 何意味。

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

const int V = 2e5, N = V + 5;

namespace sgt
{
    set<int> s[N << 2];
    int a[N << 2], typ[N << 2];
    void init()
    {
        memset(a, 0x3f, sizeof a);
        for(int i = 0; i <= V; i ++) s[1].insert(i);
        a[1] = 0;
    }
    void pushup(int x, int typ)
    {
        if(typ) return a[x] = (s[x].empty() ? 0x3f3f3f3f : *s[x].begin()), void();
        a[x] = max(a[x << 1], a[x << 1 | 1]);
        if(!s[x].empty()) a[x] = min(a[x], *s[x].begin());   
    }
    void update(int ql, int qr, int l, int r, int x, int g, int v)
    {
        if(ql <= l && r <= qr)
        {
            if(s[x].count(v))
                s[x].erase(v), pushup(x, l == r);
            return;
        }
        int mid = (l + r) >> 1, st = 0;
        if(s[x].count(v)) g = 1, s[x].erase(v);
        if(mid >= ql) update(ql, qr, l, mid, x << 1, g, v), st |= 1;
        if(mid < qr) update(ql, qr, mid + 1, r, x << 1 | 1, g, v), st |= 2;
        if(g)
        {
            if(!(st & 1)) s[x << 1].insert(v), pushup(x << 1, l == mid);
            if(!(st & 2)) s[x << 1 | 1].insert(v), pushup(x << 1 | 1, mid + 1 == r);
        }
        pushup(x, l == r);
    }
    int query(int ql, int qr, int l, int r, int x)
    {
        if(ql <= l && r <= qr)
            return a[x];
        int mid = (l + r) >> 1, ans = 0;
        if(mid >= ql) ans = max(ans, query(ql, qr, l, mid, x << 1));
        if(mid < qr) ans = max(ans, query(ql, qr, mid + 1, r, x << 1 | 1));
        ans = min(ans, a[x]);
        return ans;
    }
}

using pii = pair<int, int>;
set<pii> s[N];

set<pii>::iterator split(set<pii> &s, int x)
{
    auto it = s.upper_bound({x, V + 1});
    if(it == s.begin()) return it;
    if((--it)->second < x) return ++it;
    pii y = *it;s.erase(it);
    if(x > y.first) s.insert({y.first, x - 1});
    return s.insert({max(y.first, x), y.second}).first;
}

signed main()
{
    ios::sync_with_stdio(0);cin.tie(0);
    int q;cin >> q;
    
    sgt::init();
    for(int i = 0; i <= V; i ++) s[i].insert({1, V});

    int ans = 0;
    while(q --)
    {
        int op;cin >> op;
        if(op == 1)
        {
            int x, a, b;cin >> x >> a >> b;
            x ^= ans, a ^= ans, b ^= ans;
            auto itb = split(s[x], b + 1);
            auto ita = split(s[x], a);
            for(auto it1 = ita; it1 != itb; it1 ++)
                sgt::update(it1->first, it1->second, 1, V, 1, 0, x);
            s[x].erase(ita, itb);
        }
        else
        {
            int l, r;cin >> l >> r;
            l ^= ans, r ^= ans;
            int aans = sgt::query(l, r, 1, V, 1);
            cout << aans << "\n";
            ans += aans;
        }
    }

    return 0;
}
posted @ 2023-08-22 18:01  adam01  阅读(107)  评论(0)    收藏  举报