[CFgym103260K] Rectangle Painting
题目链接 Rectangle Painting
给 \(n\) 个集合 \(S_i\),两种操作,
1 x l r向 \(S_l\) 到 \(S_r\) 插入 \(x\)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)\)。
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;
}

浙公网安备 33010602011771号