传送门 AtCoder ABC324G Generate Arrays
逆则操作顺序考虑,可以看作至多 n n n 个联通分量不断合并的过程,此时使用启发式合并,即规模较小的连通分量向规模较大的连通分量合并,以单个元素合并为基本运算,则基本运算次数为 O ( n log n ) O(n\log n) O(nlogn)。
使用 std::set 分别维护以索引和值为关键字的集合,每次分裂出较小的分量即可。关于如何计算出所分裂两个分量的规模的问题,对于以索引为关键字的平衡树,操作直接给出了右侧元素的相对位置 x i x_i xi,则规模可以直接计算;而对于以值为关键字的平衡树,操作仅给出了需要大于的值 x i x_i xi,此时平衡树无法直接计算相对位置,那么可以二分出第一个满足 v a l > x i val>x_i val>xi 的位置,使用两个迭代器分别不断左右移动直到边界,则在操作数限制为较小分量规模大小的约束下,计算出了分裂的两个分量的规模。总时间复杂度 O ( n log 2 n ) O(n\log^2 n) O(nlog2n)。
#include
using namespace std;
using ll = long long;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
vector<int> a(n);
for (int i = 0; i < n; ++i) {
cin >> a[i];
}
int q;
cin >> q;
using pst = set<pair<int, int>>;
vector<pst> pos(q + 1), val(q + 1);
for (int i = 0; i < n; ++i) {
pos[0].insert({i, a[i]});
val[0].insert({a[i], i});
}
for (int i = 1; i <= q; ++i) {
int t, s, x;
cin >> t >> s >> x;
auto change = [](int a, int b, pst &_pos, pst &_val, pst &pos, pst &val) {
if (a > b) {
for (int j = 0; j < b; ++j) {
auto [k, a] = *prev(_pos.end());
_pos.erase({k, a});
_val.erase({a, k});
pos.insert({k, a});
val.insert({a, k});
}
} else {
swap(_pos, pos);
swap(_val, val);
for (int j = 0; j < a; ++j) {
auto [k, a] = *pos.begin();
pos.erase({k, a});
val.erase({a, k});
_pos.insert({k, a});
_val.insert({a, k});
}
}
};
auto &_pos = pos[s];
auto &_val = val[s];
int a = -1, b = -1;
if (t == 1) {
int _n = _pos.size();
if (x < _n) {
a = x, b = _n - x;
} else {
a = _n, b = 0;
}
change(a, b, _pos, _val, pos[i], val[i]);
} else {
int _n = _val.size();
auto it = _val.upper_bound({x, 1e9});
if (it == _val.end()) {
a = _n, b = 0;
} else if (it == _val.begin()) {
a = 0, b = _n;
} else {
a = b = 0;
auto it2 = prev(it);
for (;;) {
a += 1;
b += 1;
if (it2 == _val.begin()) {
b += _n - 2 * a;
break;
}
it2 = prev(it2);
it = next(it);
if (it == _val.end()) {
a += _n - 2 * a;
break;
}
}
}
change(a, b, _val, _pos, val[i], pos[i]);
}
cout << (int)pos[i].size() << '\n';
}
return 0;
}