Algorithm
Task
给定一个长度为 (n) 的序列,要求支持单点修改和区间 (kth) 查询,强制在线。
Limitation
如果认为输入数据全部与 (n) 同阶,要求算法时空复杂度 (O(n log^2n))
Solution
其实这个东西和可持久化线段树没有半毛钱关系,实质上就是树状数组套权值线段树
考虑如果不带修,那么可以直接主席树解决。主席树的本质是在每个位置维护一个从 (1) 到该位置的前缀权值线段树。
如果暴力上主席树,单次修改是 (O(n log n)) 的,导致GG。
考虑将前缀的权值线段树分成多段,这几段权值线段树相加即是总的前缀权值线段树。如果分成了 (O(T)) 段,那么修改复杂度将是 (O(T log n))。注意这里分成多段以后每一段是区间内的普通权值线段树,而不是主席树。
考虑用树状数组维护这个前缀,那么每个前缀区间都能够被分成 (O(log n)) 段,每一段是对应区间的权值线段树,这样单次修改的复杂度是 (O(log^2n))。
考虑查询前缀,只需要找出这 (O(log n)) 个前缀,然后一起二分即可。时间复杂度 (O(log^2n))
于是总时间复杂度 (O(n log^2n))使用动态开点可以做到空间复杂度 (O(n log^2n))。
Sample
Description
给定一个长度为 (n) 的序列,有 (m) 次操作,要么单点修改,要么查询区间 (kth)。
Limitation
(1 leq n,~m leq 10^5)
序列值域在 (10^9) 范围内
Solution
板板题要什么Solution
Code
我的代码常数好大啊……是 @DDOSvoid 大爷常数的两倍…… 感觉常数主要大在了查询的时候存 (O(log n)) 个节点的 std::vector
上了,大概换成手写会快好多叭……
#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
const int maxn = 100005;
int n, m, upceil;
int MU[maxn];
std::vector<int>tmp;
struct Tree {
Tree *ls, *rs;
int v;
Tree() : ls(NULL), rs(NULL), v(0) {}
};
Tree *rot[maxn];
struct Ask {
char ch;
int a, b, c;
};
Ask ask[maxn];
void init_hash();
void update(int p, const int v);
int Query(int l, int r, int k);
void Update(const int p, const int v);
void query(std::vector<int> &a, int &v);
void update(Tree *const u, const int l, const int r, const int p, const int v);
int main() {
freopen("1.in", "r", stdin);
qr(n); qr(m);
rot[0] = new Tree;
for (int i = 1; i <= n; ++i) {
rot[i] = new Tree;
qr(MU[i]);
}
char ch; int a, b, c;
for (int M = 1; M <= m; ++M) {
do ch = IPT::GetChar(); while ((ch != 'Q') && (ch != 'C'));
if (ch == 'Q') {
a = b = c = 0; qr(a); qr(b); qr(c);
} else {
a = b = 0; qr(a); qr(b);
}
ask[M] = {ch, a, b, c};
}
init_hash();
for (int i = 1; i <= n; ++i) {
update(i, 1);
}
for (int i = 1; i <= m; ++i) {
ch = ask[i].ch; a = ask[i].a; b = ask[i].b; c = ask[i].c;
if (ch == 'Q') {
qw(Query(a, b, c), '
', true);
} else {
Update(a, b);
}
}
return 0;
}
void init_hash() {
for (int i = 1; i <= n; ++i) tmp.push_back(MU[i]);
for (int i = 1; i <= m; ++i) if (ask[i].ch == 'C') {
tmp.push_back(ask[i].b);
}
std::sort(tmp.begin(), tmp.end());
upceil = std::unique(tmp.begin(), tmp.end()) - tmp.begin();
auto ed = tmp.begin() + upceil;
for (int i = 1; i <= n; ++i) {
MU[i] = std::lower_bound(tmp.begin(), ed, MU[i]) - tmp.begin() + 1;
}
for (int i = 1; i <= m; ++i) if (ask[i].ch == 'C') {
ask[i].b = std::lower_bound(tmp.begin(), ed, ask[i].b) - tmp.begin() + 1;
}
}
inline int lowbit(const int x) { return x & -x; }
void update(int p, const int v) {
int pv = MU[p];
do update(rot[p], 1, upceil, pv, v); while ((p += lowbit(p)) <= n);
}
void update(Tree *const u, const int l, const int r, const int p, const int v) {
u->v += v;
if (l == r) return;
int mid = (l + r) >> 1;
if (p <= mid) {
update(u->ls ? u->ls : u->ls = new Tree, l, mid, p, v);
} else {
update(u->rs ? u->rs : u->rs = new Tree, mid + 1, r, p, v);
}
}
void Update(const int p, const int v) {
update(p, -1);
MU[p] = v;
update(p, 1);
}
void query(std::vector<Tree*> &a, int &v) {
if (!a.size()) return;
for (auto u : a) if (u->ls) {
v += u->ls->v;
}
}
void cls(std::vector<Tree*> &a, std::vector<Tree*> &b) {
for (auto u : a) if (u->ls) {
b.push_back(u->ls);
}
}
void crs(std::vector<Tree*> &a, std::vector<Tree*> &b) {
for (auto u : a) if (u->rs) {
b.push_back(u->rs);
}
}
int Query(int l, int r, int k) {
int tl = 1, tr = upceil; --l;
std::vector<Tree*> vl[2], vr[2];
int key = 0, tk = 1;
do vr[0].push_back(rot[r]); while (r -= lowbit(r));
do vl[0].push_back(rot[l]); while (l -= lowbit(l));
while (tl != tr) {
int mid = (tl + tr) >> 1;
int s1 = 0, s2 = 0;
query(vr[key], s1); query(vl[key], s2);
int sum = s1 - s2;
if (sum >= k) {
cls(vr[key], vr[tk]);
cls(vl[key], vl[tk]);
tr = mid;
} else {
crs(vr[key], vr[tk]);
crs(vl[key], vl[tk]);
tl = mid + 1;
k -= sum;
}
vr[key].clear(); vl[key].clear();
std::swap(key, tk);
}
return tmp[tl - 1];
}