Description
小 N 不会写代码,所以他只会简单数据结构,于是他找到了一个基础线段树练习题。
在数轴上,每个自然数点都有一个可重集 \(A_0, A_1, A_2, \ldots\),初始时均为空。
你要支持以下操作 \(q\) 次:
- \(x, a, b\):对 \(\forall a \le i \le b\),在 \(A_i\) 中插入元素 \(x\)。
- \(l, r\):求 \(\displaystyle \max_{l \le i \le r} \operatorname{mex} A_i\)。
由于这太简单了,所以这个题强制在线。
\(1\leq q\leq 10^5,0\leq a,b,l,r,x\leq 2\times 10^5\)。
Solution
首先考虑不强制在线怎么做。
对于单个集合,注意到向它里面插入元素 \(x\) 是不好维护 \(\text{mex}\) 的,但是如果是删除元素 \(x\),就可以直接让 \(\text{mex}\leftarrow\min(\text{mex},x)\)。
所以离线时可以先把所有集合最终的 \(\text{mex}\) 求出来,再倒着删除插入操作,并进行区间 \(\text{chkmin}\) 即可。
回到这道题。
考虑对于每个 \(x\),用线段树计算它对哪些集合造成贡献。
然后维护集合 \(B_i\) 表示所有 \(A_i\) 中没有出现过的元素,初始 \(B_i=\mathbb{N}^*\),那么问题变为:
- 区间删除一个元素 \(x\)。
- 查询 \(\displaystyle \max_{l \le i \le r} \operatorname{mex} B_i\)。
再用 set 维护所有极长区间,满足 \(x\) 在这些区间的 \(B_i\) 中出现了,那么 \(x\) 就只能对这个 set 里的区间造成贡献。容易用颜色段均摊做。
计算区间贡献时就在每个线段树节点上维护个 set 即可。
时间复杂度:\(O(n\log n+q\log^2n)\)。
Code
#include <bits/stdc++.h>// #define int int64_tusing i64 = int64_t;const int kMaxN = 2e5 + 5;int n = 2e5, q;
std::set<std::pair<int, int>> seg[kMaxN];struct SGT {int val[kMaxN * 4];std::set<int> st[kMaxN * 4];void pushup(int x) {val[x] = std::min(!st[x].size() ? n : *st[x].begin(), std::max(val[x << 1], val[x << 1 | 1]));}void build(int x, int l, int r) {val[x] = n;if (l == r) return;int mid = (l + r) >> 1;build(x << 1, l, mid), build(x << 1 | 1, mid + 1, r);}void update(int x, int l, int r, int ql, int qr, int v, bool op = 1) {if (l > qr || r < ql) {return;} else if (l >= ql && r <= qr) {if (op) st[x].emplace(v);else st[x].erase(v);return l == r ? void(val[x] = (!st[x].size() ? n : *st[x].begin())) : pushup(x);}int mid = (l + r) >> 1;update(x << 1, l, mid, ql, qr, v, op), update(x << 1 | 1, mid + 1, r, ql, qr, v, op);pushup(x);}int query(int x, int l, int r, int ql, int qr) {if (l > qr || r < ql) return 0;else if (l >= ql && r <= qr) return val[x];int mid = (l + r) >> 1;return std::min(!st[x].size() ? n : *st[x].begin(), std::max(query(x << 1, l, mid, ql, qr), query(x << 1 | 1, mid + 1, r, ql, qr)));}
} sgt;void split(int x, int p) {auto it = seg[x].lower_bound({p + 1, 0});if (it == seg[x].begin()) return;--it;auto [l, r] = *it;if (l == p || r < p) return;sgt.update(1, 0, n, l, r, x, 0), sgt.update(1, 0, n, l, p - 1, x, 1), sgt.update(1, 0, n, p, r, x, 1);seg[x].erase(it), seg[x].emplace(l, p - 1), seg[x].emplace(p, r);
}void ins(int l, int r, int v) {split(v, l), split(v, r + 1);for (auto it = seg[v].lower_bound({l, 0}); it != seg[v].end() && it->second <= r; it = seg[v].lower_bound({l, 0})) {sgt.update(1, 0, n, it->first, it->second, v, 0);seg[v].erase(it);}
}int query(int l, int r) { return sgt.query(1, 0, n, l, r); }void dickdreamer() {std::cin >> q;sgt.build(1, 0, n);for (int i = 0; i <= n; ++i) seg[i].emplace(0, n), sgt.update(1, 0, n, 0, n, i, 1);i64 S = 0;for (int i = 1; i <= q; ++i) {int op;i64 v, l, r;std::cin >> op;if (op == 1) {std::cin >> v >> l >> r;v ^= S, l ^= S, r ^= S;ins(l, r, v);} else {std::cin >> l >> r;l ^= S, r ^= S;int res = query(l, r);std::cout << res << '\n';S += res;}}
}int32_t main() {
#ifdef ORZXKRfreopen("in.txt", "r", stdin);freopen("out.txt", "w", stdout);
#endifstd::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);int T = 1;// std::cin >> T;while (T--) dickdreamer();// std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";return 0;
}