【XCPC模板整理 - 第一期】数据结构
前言
这是我个人使用的一些模板封装。
限于个人能力,可能存在诸多不足与漏洞,在未加测试直接使用前请务必小心谨慎。更新可能会滞后于我本地的文档,如有疑问或者催更之类的可以在评论区留言。
全文模板测试均基于以下版本信息,请留意版本兼容问题。
Windows, 64bit
G++ (ISO C++20)
stack=268435456
开启O2优化
同时,可能还使用到了如下宏定义:
#define int long long
#define endl "\n"
using LL = long long;
using ld = long double;
using PII = pair<int, int>;
using TII = tuple<int, int, int>;
并查集部分
基础封装
通常意义上我们默认增加“路径压缩”优化,时间复杂度为:查询 \(\mathcal O (1)\) ,合并接近于 \(\mathcal O(\alpha(n))\) (这里 \(\alpha\) 代表的是反阿克曼函数,一般看作是一个极小的常数)。
struct DSU {
vector<int> fa, p;
DSU(int n) {
fa.resize(n + 1);
iota(fa.begin(), fa.end(), 0);
p.resize(n + 1, 1);
}
int get(int x) {
while (x != fa[x]) {
x = fa[x] = fa[fa[x]];
}
return x;
}
bool merge(int x, int y) { // 设x是y的祖先
x = get(x), y = get(y);
if (x == y) return false;
if (x < y) swap(x, y); // 将编号小的合并到大的上
fa[y] = x;
p[x] += p[y];
return true;
}
bool same(int x, int y) {
return get(x) == get(y);
}
int size(int x) { // 输出连通块中点的数量
return p[get(x)];
}
};
求解连通块内部点、边、环信息
并查集算法除了常规意义上的寻找(时间序意义上的)祖先节点、寻找块内最小/最大的节点之外,还可以用极小的时间代价维护连通块内点的数量、边的数量、是否存在自环等等内容。除了“路径压缩”优化外,还有“启发式合并”优化能够进一步缩短合并花费的时间,然而这会影响上述罗列的最大/最小节点查找,故一般我个人习惯不添加这一优化。
struct DSU {
vector<int> fa, p, e, f;
DSU(int n) {
fa.resize(n + 1);
iota(fa.begin(), fa.end(), 0);
p.resize(n + 1, 1);
e.resize(n + 1);
f.resize(n + 1);
}
int get(int x) {
while (x != fa[x]) {
x = fa[x] = fa[fa[x]];
}
return x;
}
bool merge(int x, int y) { // 设x是y的祖先
if (x == y) f[get(x)] = 1;
x = get(x), y = get(y);
e[x]++;
if (x == y) return false;
if (x < y) swap(x, y); // 将编号小的合并到大的上
fa[y] = x;
f[x] |= f[y], p[x] += p[y], e[x] += e[y];
return true;
}
bool same(int x, int y) {
return get(x) == get(y);
}
bool F(int x) { // 判断连通块内是否存在自环
return f[get(x)];
}
int size(int x) { // 输出连通块中点的数量
return p[get(x)];
}
int E(int x) { // 输出连通块中边的数量
return e[get(x)];
}
};
维护路径信息(带权并查集)
留坑。
树状数组部分
基础封装
struct BIT {
int n;
vector<int> w;
BIT(int n) {
this->n = n; // 这里必须写 n ,否则会RE
w.resize(n + 1);
}
void add(int x, int k) {
for (; x <= n; x += x & -x) {
w[x] += k;
}
}
void add(int x, int y, int k) { // 区间修改
add(x, k), add(y, -k);
}
int ask(int x) {
int ans = 0;
for (; x; x -= x & -x) {
ans += w[x];
}
return ans;
}
int ask(int x, int y) { // 区间查询
return ask(y) - ask(x - 1);
}
int kth(int k) { // ex: 查找第k大的值
int ans = 0;
for (int i = __lg(n); i >= 0; i--) {
int val = ans + (1 << i);
if (val < n && w[val] < k) {
k -= w[val];
ans = val;
}
}
return ans + 1;
}
};
求解逆序对
struct BIT {
int n;
vector<int> w;
BIT() {}
void add(int x, int k) {
for (; x <= n; x += x & -x) {
w[x] += k;
}
}
int ask(int x) {
int ans = 0;
for (; x; x -= x & -x) {
ans += w[x];
}
return ans;
}
int ask(int x, int y) {
return ask(y) - ask(x - 1);
}
int get(auto val) { // 获取逆序对数量
this->n = val.size() - 1; // 注意 n 不能 +1
w.resize(n + 1);
vector<pair<int, int>> alls;
for (int i = 1; i <= n; i++) {
alls.emplace_back(val[i], i);
}
sort(alls.begin(), alls.end());
int ans = 0;
for (auto [val, idx] : alls) {
ans += ask(idx + 1, n);
add(idx, 1);
}
return ans;
}
};
树状数组套树状数组(二维树状数组)1
请注意,该版本不能同时进行区间修改+区间查询。无离散化版本的空间占用为 \(\mathcal O(NM)\) 、建树复杂度为 \(\mathcal O(NM)\) 、单次查询复杂度为 \(\mathcal O(\log N\cdot \log M)\) 。
大致有以下两种写法,均通过模板题测试。
该部分模板题可参考:LOJ #133. 二维树状数组 1:单点修改,区间查询、LOJ #134. 二维树状数组 2:区间修改,单点查询。
借助一维树状数组进行拓展
struct BIT_2D {
struct BIT {
int n;
vector<int> w;
BIT() {}
BIT(int n) {
this->n = n; // 这里必须写 n ,否则会RE
w.resize(n + 1);
}
void add(int x, int k) {
for (; x <= n; x += x & -x) {
w[x] += k;
}
}
int ask(int x) {
int ans = 0;
for (; x; x -= x & -x) {
ans += w[x];
}
return ans;
}
};
int n;
vector<BIT> w;
BIT_2D() {}
BIT_2D(int n, int m) {
this->n = n;
w.resize(n + 1);
for (int i = 1; i <= n; i++) {
w[i] = BIT(m);
}
}
void add(int x, int y, int k) {
for (; x <= n; x += x & -x) {
w[x].add(y, k);
}
}
void add(int x, int y, int X, int Y, int k) { // 区块修改:二维差分
X++, Y++;
add(x, y, k), add(X, y, -k);
add(X, Y, k), add(x, Y, -k);
}
int ask(int x, int y) { // 单点查询
int ans = 0;
for (; x; x -= x & -x) {
ans += w[x].ask(y);
}
return ans;
}
int ask(int x, int y, int X, int Y) { // 区块查询:二维前缀和
x--, y--;
return ask(X, Y) - ask(x, Y) - ask(X, y) + ask(x, y);
}
};
压缩优化
struct BIT_2D {
int n, m;
vector<vector<int>> w;
BIT_2D(int n, int m) : n(n), m(m) {
w.resize(n + 1, vector<int>(m + 1));
}
void add(int x, int y, int k) {
for (int i = x; i <= n; i += i & -i) {
for (int j = y; j <= m; j += j & -j) {
w[i][j] += k;
}
}
}
void add(int x, int y, int X, int Y, int k) { // 区块修改:二维差分
X++, Y++;
add(x, y, k), add(X, y, -k);
add(X, Y, k), add(x, Y, -k);
}
int ask(int x, int y) { // 单点查询
int ans = 0;
for (int i = x; i; i -= i & -i) {
for (int j = y; j; j -= j & -j) {
ans += w[i][j];
}
}
return ans;
}
int ask(int x, int y, int X, int Y) { // 区块查询:二维前缀和
x--, y--;
return ask(X, Y) - ask(x, Y) - ask(X, y) + ask(x, y);
}
};
树状数组套树状数组(二维树状数组)2
该版本支持全部操作。但是时空复杂度均比上一个版本多 \(4\) 倍。
这里仅提供压缩优化版。
该部分模板题可参考:LOJ #135. 二维树状数组 3:区间修改,区间查询。
struct BIT_2D {
int n, m;
vector<vector<int>> b1, b2, b3, b4;
BIT_2D(int n, int m) : n(n), m(m) {
b1.resize(n + 1, vector<int>(m + 1));
b2.resize(n + 1, vector<int>(m + 1));
b3.resize(n + 1, vector<int>(m + 1));
b4.resize(n + 1, vector<int>(m + 1));
}
void add(auto &w, int x, int y, int k) { // 单点修改
for (int i = x; i <= n; i += i & -i) {
for (int j = y; j <= m; j += j & -j) {
w[i][j] += k;
}
}
}
void add(int x, int y, int k) { // 多了一步计算
add(b1, x, y, k);
add(b2, x, y, k * (x - 1));
add(b3, x, y, k * (y - 1));
add(b4, x, y, k * (x - 1) * (y - 1));
}
void add(int x, int y, int X, int Y, int k) { // 区块修改:二维差分
X++, Y++;
add(x, y, k), add(X, y, -k);
add(X, Y, k), add(x, Y, -k);
}
int ask(auto &w, int x, int y) { // 单点查询
int ans = 0;
for (int i = x; i; i -= i & -i) {
for (int j = y; j; j -= j & -j) {
ans += w[i][j];
}
}
return ans;
}
int ask(int x, int y) { // 多了一步计算
int ans = 0;
ans += x * y * ask(b1, x, y);
ans -= y * ask(b2, x, y);
ans -= x * ask(b3, x, y);
ans += ask(b4, x, y);
return ans;
}
int ask(int x, int y, int X, int Y) { // 区块查询:二维前缀和
x--, y--;
return ask(X, Y) - ask(x, Y) - ask(X, y) + ask(x, y);
}
};
树状数组套树状数组(二维树状数组)3
第一个版本加上了离散化之后的效果,可以用来解决 这一道二维树状数组的题目,但是有一些细节我还没研究清楚,暂时留坑。
我的提交。
struct BIT_2D {
struct Compress {
int n;
vector<int> alls;
Compress() {}
void add(int x) {
alls.emplace_back(x);
}
void init() {
alls.emplace_back(numeric_limits<int>::max());
sort(alls.begin(), alls.end());
alls.erase(unique(alls.begin(), alls.end()), alls.end());
this->n = alls.size();
}
int operator[](int x) { // 返回 x 元素的新下标
return upper_bound(alls.begin(), alls.end(), x) - alls.begin();
}
int get(int x) { // 返回 x 元素的新下标
return lower_bound(alls.begin(), alls.end(), x) - alls.begin();
}
};
int n;
vector<vector<int>> w;
vector<pair<int, int>> ver;
Compress row;
vector<Compress> col;
BIT_2D() {}
void merge(int x, int y) {
ver.emplace_back(x, y);
}
void init() {
for (auto [x, y] : ver) { // 离散化横坐标
row.add(x);
}
row.init();
this->n = row.n;
w.resize(n + 1);
col.resize(n + 1);
for (auto [x, y] : ver) { // 对每一行的纵坐标离散化,以此节约空间
for (int i = row[x]; i <= n; i += i & -i) {
col[i].add(y);
}
}
for (int i = 1; i <= n; i++) {
col[i].init();
w[i].resize(col[i].n);
}
}
void add(int x, int y, int k) {
for (int i = row[x]; i <= n; i += i & -i) {
for (int j = col[i][y]; j < col[i].n; j += j & -j) {
w[i][j] += k;
}
}
}
void add(int x, int y, int X, int Y, int k) { // 区块修改:二维差分
X++, Y++;
add(x, y, k), add(X, y, -k);
add(X, Y, k), add(x, Y, -k);
}
int ask(int x, int y) { // 单点查询
int ans = 0;
for (int i = row[x]; i; i -= i & -i) {
for (int j = col[i][y]; j; j -= j & -j) {
ans += w[i][j];
}
}
return ans;
}
int ask(int x, int y, int X, int Y) { // 区块查询:二维前缀和
x--, y--;
return ask(X, Y) - ask(x, Y) - ask(X, y) + ask(x, y);
}
void debug() {
for (int i = 1; i <= 20; i++) {
for (int j = 1; j <= 20; j++) {
cout << setw(3) << ask(i, j) << " ";
}
cout << endl;
}
}
};
取模类
集成了常见的取模四则运算,运算速度与手动取模相差无几,效率极高。包含以下特点:
- 模数可为输入的数字(不需要确保是静态变量);
- 可以与
#define int long long
同时存在。
目前存在以下缺陷暂时没办法解决:
- 多个可变模数;
- 支持
long long
类型(Jiangly那边的最新封装可以解决这一问题); - 支持
x++;
操作。
// Zmod专用快速幂,如需使用,请注意相应的类型转换:如mypow((Z)2, k)
template <class T> T mypow(T n, int k, T r = 1) {
for (; k; k >>= 1, n *= n) {
if (k & 1) r *= n;
}
return r;
}
int MOD = 998244353;
struct Z {
int x;
Z(int x = 0) : x(norm(x % MOD)) {}
int val() const {
return x;
}
int norm(int x) const {
return (x + MOD) % MOD;
}
Z operator-() const {
int val = norm(MOD - x);
return Z(val);
}
Z inv() const {
assert(x != 0);
return mypow(*this, MOD - 2);
}
Z &operator*=(const Z &i) {
return x = x * i.x % MOD, *this;
}
Z &operator+=(const Z &i) {
return x = norm(x + i.x), *this;
}
Z &operator-=(const Z &i) {
return x = norm(x - i.x), *this;
}
Z &operator/=(const Z &i) {
return *this *= i.inv();
}
Z &operator%=(const int &i) {
return x %= i, *this;
}
friend Z operator*(const Z &i, const Z &j) {
Z res = i;
res *= j;
return res;
}
friend Z operator+(const Z &i, const Z &j) {
Z res = i;
res += j;
return res;
}
friend Z operator-(const Z &i, const Z &j) {
Z res = i;
res -= j;
return res;
}
friend Z operator/(const Z &i, const Z &j) {
Z res = i;
res /= j;
return res;
}
friend Z operator%(const Z &i, const int &j) {
Z res = i;
res %= j;
return res;
}
friend auto &operator>>(istream &it, Z &j) {
int v;
it >> v;
j = Z(v);
return it;
}
friend auto &operator<<(ostream &o, const Z &j) {
return o << j.x;
}
bool operator<(const Z &i) const {
return x < i.x;
}
bool operator>(const Z &i) const {
return x > i.x;
}
bool operator==(const Z &i) const {
return x == i.x;
}
bool operator!=(const Z &i) const {
return x != i.x;
}
};
线段树部分
标准封装(区间加法+区间最小值+区间合并)
由于有时候会取模,故采用了可变封装。
template <class T> struct Segt_ {
struct node {
int l, r;
T w, Min;
T lazy;
};
vector<T> w;
vector<node> t;
Segt_(int n) {
w.resize(n + 1);
t.resize((n << 2) + 1);
build(1, n);
}
Segt_(vector<int> in) {
int n = in.size() - 1;
w.resize(n + 1);
for (int i = 1; i <= n; i++) {
w[i] = in[i];
}
t.resize((n << 2) + 1);
build(1, n);
}
void pushdown(node &p, T lazy) { // 在此更新下递函数
p.w += (p.r - p.l + 1) * lazy;
p.Min += lazy;
p.lazy += lazy;
}
void pushup(node &p, node &l, node &r) { // 在此更新上传函数
p.w = l.w + r.w;
p.Min = min(l.Min, r.Min);
}
#define GL (k << 1)
#define GR (k << 1 | 1)
void pushdown(int k) { // 不需要动
if (t[k].lazy == 0) return;
pushdown(t[GL], t[k].lazy);
pushdown(t[GR], t[k].lazy);
t[k].lazy = 0;
}
void pushup(int k) { // 不需要动
pushup(t[k], t[GL], t[GR]);
}
void build(int l, int r, int k = 1) {
if (l == r) {
t[k] = {l, r, w[l], w[l]};
return;
}
t[k] = {l, r};
int mid = (l + r) / 2;
build(l, mid, GL);
build(mid + 1, r, GR);
pushup(k);
}
void modify(int l, int r, T val, int k = 1) { // 区间修改
if (l <= t[k].l && t[k].r <= r) {
pushdown(t[k], val);
return;
}
pushdown(k);
int mid = (t[k].l + t[k].r) / 2;
if (l <= mid) modify(l, r, val, GL);
if (mid < r) modify(l, r, val, GR);
pushup(k);
}
T Min(int l, int r, int k = 1) { // 区间询问最小值
if (l <= t[k].l && t[k].r <= r) {
return t[k].Min;
}
pushdown(k);
int mid = (t[k].l + t[k].r) / 2;
T ans = 1E18;
if (l <= mid) ans = min(ans, Min(l, r, GL));
if (mid < r) ans = min(ans, Min(l, r, GR));
return ans;
}
T ask(int l, int r, int k = 1) { // 区间询问,不合并
if (l <= t[k].l && t[k].r <= r) {
return t[k].w;
}
pushdown(k);
int mid = (t[k].l + t[k].r) / 2;
T ans = 0;
if (l <= mid) ans += ask(l, r, GL);
if (mid < r) ans += ask(l, r, GR);
return ans;
}
node Ask(int l, int r, int k = 1) { // 区间合并
if (l <= t[k].l && t[k].r <= r) {
return t[k];
}
pushdown(k);
int mid = (t[k].l + t[k].r) / 2;
// 区间合并这里的if是反过来的
if (r <= mid) return Ask(l, r, GL);
if (mid < l) return Ask(l, r, GR);
auto left = Ask(l, r, GL), right = Ask(l, r, GR);
node res = {0, 0, 0, 0};
pushup(res, left, right); // 合并left和right为新区间
return res;
}
void debug(int k = 1) {
cout << "[" << t[k].l << ", " << t[k].r << "]: ";
cout << "w = " << t[k].w << ", ";
cout << "Min = " << t[k].Min << ", ";
cout << "lazy = " << t[k].lazy << ", ";
cout << endl;
if (t[k].l == t[k].r) return;
debug(GL), debug(GR);
}
#undef GL
#undef GR
};
using Segt = Segt_<int>;
同时需要处理区间加法与乘法修改
该部分模板题可参考:洛谷P3373 【模板】线段树 2,难点在于懒标记的优先级问题。
template <class T> struct Segt_ {
struct node {
int l, r;
T w, add, mul = 1; // 注意初始赋值
};
vector<T> w;
vector<node> t;
Segt_(int n) {
w.resize(n + 1);
t.resize((n << 2) + 1);
build(1, n);
}
Segt_(vector<int> in) {
int n = in.size() - 1;
w.resize(n + 1);
for (int i = 1; i <= n; i++) {
w[i] = in[i];
}
t.resize((n << 2) + 1);
build(1, n);
}
void pushdown(node &p, T add, T mul) { // 在此更新下递函数
p.w = p.w * mul + (p.r - p.l + 1) * add;
p.add = p.add * mul + add;
p.mul *= mul;
}
void pushup(node &p, node &l, node &r) { // 在此更新上传函数
p.w = l.w + r.w;
}
#define GL (k << 1)
#define GR (k << 1 | 1)
void pushdown(int k) { // 不需要动
pushdown(t[GL], t[k].add, t[k].mul);
pushdown(t[GR], t[k].add, t[k].mul);
t[k].add = 0, t[k].mul = 1;
}
void pushup(int k) { // 不需要动
pushup(t[k], t[GL], t[GR]);
}
void build(int l, int r, int k = 1) {
if (l == r) {
t[k] = {l, r, w[l]};
return;
}
t[k] = {l, r};
int mid = (l + r) / 2;
build(l, mid, GL);
build(mid + 1, r, GR);
pushup(k);
}
void modify(int l, int r, T val, int k = 1) { // 区间修改
if (l <= t[k].l && t[k].r <= r) {
t[k].w += (t[k].r - t[k].l + 1) * val;
t[k].add += val;
return;
}
pushdown(k);
int mid = (t[k].l + t[k].r) / 2;
if (l <= mid) modify(l, r, val, GL);
if (mid < r) modify(l, r, val, GR);
pushup(k);
}
void modify2(int l, int r, T val, int k = 1) { // 区间修改
if (l <= t[k].l && t[k].r <= r) {
t[k].w *= val;
t[k].add *= val;
t[k].mul *= val;
return;
}
pushdown(k);
int mid = (t[k].l + t[k].r) / 2;
if (l <= mid) modify2(l, r, val, GL);
if (mid < r) modify2(l, r, val, GR);
pushup(k);
}
T ask(int l, int r, int k = 1) { // 区间询问,不合并
if (l <= t[k].l && t[k].r <= r) {
return t[k].w;
}
pushdown(k);
int mid = (t[k].l + t[k].r) / 2;
T ans = 0;
if (l <= mid) ans += ask(l, r, GL);
if (mid < r) ans += ask(l, r, GR);
return ans;
}
void debug(int k = 1) {
cout << "[" << t[k].l << ", " << t[k].r << "]: ";
cout << "w = " << t[k].w << ", ";
cout << "add = " << t[k].add << ", ";
cout << "mul = " << t[k].mul << ", ";
cout << endl;
if (t[k].l == t[k].r) return;
debug(GL), debug(GR);
}
#undef GL
#undef GR
};
扫描线(面积并、周长并)
暂未封装,留坑。
线段树套线段树(二维线段树、矩形树)
暂未封装,留坑。
主席树(可持久化权值线段树)
以 \(\mathcal O(N\log N)\) 的时间复杂度建树、查询、修改。下方代码为查询区间第 \(k\) 小值的模板。这部分的代码来自我的队友 \(\pmb{Hamine}\) 。
struct PresidentTree {
static constexpr int N = 2e5 + 10;
int cntNodes, root[N];
struct node {
int l, r;
int cnt;
} tr[4 * N + 17 * N];
void modify(int &u, int v, int l, int r, int x) {
u = ++cntNodes;
tr[u] = tr[v];
tr[u].cnt++;
if (l == r) return;
int mid = (l + r) / 2;
if (x <= mid)
modify(tr[u].l, tr[v].l, l, mid, x);
else
modify(tr[u].r, tr[v].r, mid + 1, r, x);
}
int kth(int u, int v, int l, int r, int k) {
if (l == r) return l;
int res = tr[tr[v].l].cnt - tr[tr[u].l].cnt;
int mid = (l + r) / 2;
if (k <= res)
return kth(tr[u].l, tr[v].l, l, mid, k);
else
return kth(tr[u].r, tr[v].r, mid + 1, r, k - res);
}
};
离散化与坐标压缩
基础封装
template <typename T> struct Compress_ {
int n, shift = 0; // shift 用于标记下标偏移量
vector<T> alls;
Compress_() {}
Compress_(auto in) : alls(in) {
init();
}
void add(T x) {
alls.emplace_back(x);
}
template <typename... Args> void add(T x, Args... args) {
add(x), add(args...);
}
void init() {
alls.emplace_back(numeric_limits<T>::max());
sort(alls.begin(), alls.end());
alls.erase(unique(alls.begin(), alls.end()), alls.end());
this->n = alls.size();
}
int size() {
return n;
}
int operator[](T x) { // 返回 x 元素的新下标
return upper_bound(alls.begin(), alls.end(), x) - alls.begin() + shift;
}
T Get(int x) { // 根据新下标返回原来元素
assert(x - shift < n);
return x - shift < n ? alls[x - shift] : -1;
}
bool count(T x) { // 查找元素 x 是否存在
return binary_search(alls.begin(), alls.end(), x);
}
friend auto &operator<<(ostream &o, const auto &j) {
cout << "{";
for (auto it : j.alls) {
o << it << " ";
}
return o << "}";
}
};
using Compress = Compress_<int>;
未封装简洁版
sort(alls.begin(), alls.end());
alls.erase(unique(alls.begin(), alls.end()), alls.end());
auto get = [&](int x) {
return lower_bound(alls.begin(), alls.end(), x) - alls.begin();
};
莫队部分
普通莫队
以 \(\mathcal O(N \sqrt N)\) 的复杂度完成 \(Q\) 次询问的离线查询,其中每个分块的大小取 \(\sqrt N=\sqrt {10^5} = 317\) ,也可以使用 ceil((double)n / (int)sqrt(n))
或者 sqrt(n)
划分。
signed main() {
int n;
cin >> n;
vector<int> w(n + 1);
for (int i = 1; i <= n; i++) {
cin >> w[i];
}
int q;
cin >> q;
vector<array<int, 3>> query(q + 1);
for (int i = 1; i <= q; i++) {
int l, r;
cin >> l >> r;
query[i] = {l, r, i};
}
int Knum = n / min<int>(n, sqrt(q)); // 计算块长
vector<int> K(n + 1);
for (int i = 1; i <= n; i++) { // 固定块长
K[i] = (i - 1) / Knum + 1;
}
sort(query.begin() + 1, query.end(), [&](auto x, auto y) {
if (K[x[0]] != K[y[0]]) return x[0] < y[0];
if (K[x[0]] & 1) return x[1] < y[1];
return x[1] > y[1];
});
int l = 1, r = 0, val = 0;
vector<int> ans(q + 1);
for (int i = 1; i <= q; i++) {
auto [ql, qr, id] = query[i];
auto add = [&](int x) -> void {
};
auto del = [&](int x) -> void {
};
while (l > ql) add(w[--l]);
while (r < qr) add(w[++r]);
while (l < ql) del(w[l++]);
while (r > qr) del(w[r--]);
ans[id] = val;
}
for (int i = 1; i <= q; i++) {
cout << ans[i] << endl;
}
}
带修莫队(带时间维度的莫队)
以 \(\mathcal O(N^\frac{5}{3})\) 的复杂度完成 \(Q\) 次询问的离线查询,其中每个分块的大小取 \(N^\frac{2}{3}=\sqrt[3]{100000^2}=2154\) (直接取会略快),也可以使用 pow(n, 0.6666)
划分。
signed main() {
int n, q;
cin >> n >> q;
vector<int> w(n + 1);
for (int i = 1; i <= n; i++) {
cin >> w[i];
}
vector<array<int, 4>> query = {{}}; // {左区间, 右区间, 累计修改次数, 下标}
vector<array<int, 2>> modify = {{}}; // {修改的值, 修改的元素下标}
for (int i = 1; i <= q; i++) {
char op;
cin >> op;
if (op == 'Q') {
int l, r;
cin >> l >> r;
query.push_back({l, r, (int)modify.size() - 1, (int)query.size()});
} else {
int idx, w;
cin >> idx >> w;
modify.push_back({w, idx});
}
}
int Knum = 2154; // 计算块长
vector<int> K(n + 1);
for (int i = 1; i <= n; i++) { // 固定块长
K[i] = (i - 1) / Knum + 1;
}
sort(query.begin() + 1, query.end(), [&](auto x, auto y) {
if (K[x[0]] != K[y[0]]) return x[0] < y[0];
if (K[x[1]] != K[y[1]]) return x[1] < y[1];
return x[3] < y[3];
});
int l = 1, r = 0, val = 0;
int t = 0; // 累计修改次数
vector<int> ans(query.size());
for (int i = 1; i < query.size(); i++) {
auto [ql, qr, qt, id] = query[i];
auto add = [&](int x) -> void {
};
auto del = [&](int x) -> void {
};
auto time = [&](int x, int l, int r) -> void {
};
while (l > ql) add(w[--l]);
while (r < qr) add(w[++r]);
while (l < ql) del(w[l++]);
while (r > qr) del(w[r--]);
while (t < qt) time(++t, ql, qr);
while (t > qt) time(t--, ql, qr);
ans[id] = val;
}
for (int i = 1; i < ans.size(); i++) {
cout << ans[i] << endl;
}
}
树链剖分/轻重链剖分
基础封装(配合线段树等数据结构使用)
本封装将线段树处理的部分分离,方便修改。支持模板题P3384 【模板】重链剖分/树链剖分的四个查询(链上查询/修改、子树查询/修改),建树时间复杂度 \(\mathcal O(N\log N)\) ,单次查询时间复杂度 \(\mathcal O(\log ^2 N)\) 。
struct HLD {
int n, idx;
vector<vector<int>> ver;
vector<int> siz, dep;
vector<int> top, son, parent;
vector<int> in, id, val;
Segt segt;
HLD(int n) {
this->n = n;
ver.resize(n + 1);
siz.resize(n + 1);
dep.resize(n + 1);
top.resize(n + 1);
son.resize(n + 1);
parent.resize(n + 1);
idx = 0;
id.resize(n + 1);
val.resize(n + 1);
}
void add(int x, int y) { // 建立双向边
ver[x].push_back(y);
ver[y].push_back(x);
}
void dfs1(int x) {
siz[x] = 1;
dep[x] = dep[parent[x]] + 1;
for (auto y : ver[x]) {
if (y == parent[x]) continue;
parent[y] = x;
dfs1(y);
siz[x] += siz[y];
if (siz[y] > siz[son[x]]) {
son[x] = y;
}
}
}
void dfs2(int x, int up) {
id[x] = ++idx;
val[idx] = in[x]; // 建立编号
top[x] = up;
if (son[x]) dfs2(son[x], up);
for (auto y : ver[x]) {
if (y == parent[x] || y == son[x]) continue;
dfs2(y, y);
}
}
void work(int root, auto in) { // 在此初始化
this->in = in;
dfs1(root);
dfs2(root, root);
segt.init(val); // 建立线段树
}
void modify(int l, int r, int val) { // 链上修改
while (top[l] != top[r]) {
if (dep[top[l]] < dep[top[r]]) {
swap(l, r);
}
segt.modify(id[top[l]], id[l], val);
l = parent[top[l]];
}
if (dep[l] > dep[r]) {
swap(l, r);
}
segt.modify(id[l], id[r], val);
}
void modify(int root, int val) { // 子树修改
segt.modify(id[root], id[root] + siz[root] - 1, val);
}
int ask(int l, int r) { // 链上查询
int ans = 0;
while (top[l] != top[r]) {
if (dep[top[l]] < dep[top[r]]) {
swap(l, r);
}
ans += segt.ask(id[top[l]], id[l]);
l = parent[top[l]];
}
if (dep[l] > dep[r]) {
swap(l, r);
}
return ans + segt.ask(id[l], id[r]);
}
int ask(int root) { // 子树查询
return segt.ask(id[root], id[root] + siz[root] - 1);
}
};
求解最近公共祖先
预处理时间复杂度 \(\mathcal O(N)\) ;单次查询 \(\mathcal O(\log N)\) ,比树上倍增法求解最近公共祖先常数更小。
struct HLD {
int n, idx;
vector<vector<int>> ver;
vector<int> siz, dep;
vector<int> top, son, parent;
HLD(int n) {
this->n = n;
ver.resize(n + 1);
siz.resize(n + 1);
dep.resize(n + 1);
top.resize(n + 1);
son.resize(n + 1);
parent.resize(n + 1);
}
void add(int x, int y) { // 建立双向边
ver[x].push_back(y);
ver[y].push_back(x);
}
void dfs1(int x) {
siz[x] = 1;
dep[x] = dep[parent[x]] + 1;
for (auto y : ver[x]) {
if (y == parent[x]) continue;
parent[y] = x;
dfs1(y);
siz[x] += siz[y];
if (siz[y] > siz[son[x]]) {
son[x] = y;
}
}
}
void dfs2(int x, int up) {
top[x] = up;
if (son[x]) dfs2(son[x], up);
for (auto y : ver[x]) {
if (y == parent[x] || y == son[x]) continue;
dfs2(y, y);
}
}
void work(int root) { // 在此初始化
dfs1(root);
dfs2(root, root);
}
int lca(int x, int y) {
while (top[x] != top[y]) {
if (dep[top[x]] > dep[top[y]]) {
x = parent[top[x]];
} else {
y = parent[top[y]];
}
}
return dep[x] < dep[y] ? x : y;
}
int clac(int x, int y) { // 查询两点间距离
return dep[x] + dep[y] - 2 * dep[lca(x, y)];
}
};
分数运算类
定义了分数的四则运算,如果需要处理浮点数,那么需要将函数中的 gcd
运算替换为 fgcd
。
template<class T> struct Frac {
T x, y;
Frac(T x_, T y_) : x(x_), y(y_) {
if (y < 0) {
y = -y;
x = -x;
}
}
Frac() : Frac(0, 1) {}
Frac(T x_) : Frac(x_, 1) {}
constexpr long double val() {
return (long double)(x) / y;
}
constexpr Frac norm() { // 调整符号、转化为最简形式
T p = gcd(x, y);
return {x / p, y / p};
}
Frac &operator+=(const Frac &it) {
x = x * it.y + it.x * y;
y *= it.y;
return *this;
}
Frac &operator-=(const Frac &it) {
x = x * it.y - it.x * y;
y *= it.y;
return *this;
}
Frac &operator*=(const Frac &it) {
x *= it.x;
y *= it.y;
return *this;
}
Frac &operator/=(const Frac &it) {
x *= it.y;
y *= it.x;
if (y < 0) {
x = -x;
y = -y;
}
return *this;
}
friend Frac operator+(Frac ans, const Frac &it) { return ans += it; }
friend Frac operator-(Frac ans, const Frac &it) { return ans -= it; }
friend Frac operator*(Frac ans, const Frac &it) { return ans *= it; }
friend Frac operator/(Frac ans, const Frac &it) { return ans /= it; }
friend Frac operator-(const Frac &ans) { return Frac(-ans.x, ans.y); }
friend bool operator<(const Frac &ans, const Frac &it) {
return ans.x * it.y < it.x * ans.y;
}
friend bool operator>(const Frac &ans, const Frac &it) {
return ans.x * it.y > it.x * ans.y;
}
friend bool operator==(const Frac &ans, const Frac &it) {
return ans.x * it.y == it.x * ans.y;
}
friend bool operator!=(const Frac &ans, const Frac &it) {
return ans.x * it.y != it.x * ans.y;
}
friend std::ostream &operator<<(std::ostream &os, Frac it) {
T p = fgcd(it.x, it.y);
if (it.y == p) {
return os << it.x / p;
} else {
return os << it.x / p << "/" << it.y / p;
}
}
};
平衡二叉树部分
pd_ds库
懒得手写,所以直接使用 \(\tt pd_ds\) 库实现,记得加上相应的头文件,同时需要注意定义时的参数,一般只需要修改第三个参数:即定义的是大根堆还是小根堆。
附常见成员函数:
empty() / size() insert(x) // 插入元素x erase(x) // 删除元素/迭代器x order_of_key(x) // 返回元素x的排名 find_by_order(x) // 返回排名为x的元素迭代器 lower_bound(x) / upper_bound(x) // 返回迭代器 join(Tree) // 将Tree树的全部元素并入当前的树 split(x, Tree) // 将大于x的元素放入Tree树
#include <bits/extc++.h>
using namespace __gnu_pbds;
bool Solve() {
using PII = pair<int, int>;
tree<PII, null_type, less<PII>, rb_tree_tag, tree_order_statistics_node_update> ver;
map<int, int> dic;
int n; cin >> n;
while (n--) {
int op, x;
cin >> op >> x;
if (op == 1) { // 插入一个元素x,允许重复
ver.insert({x, ++dic[x]});
} else if (op == 2) { // 删除元素x,若有重复,则任意删除一个
ver.erase({x, dic[x]--});
} else if (op == 3) { // 查询元素x的排名(排名定义为比当前数小的数的个数+1)
cout << ver.order_of_key({x, 1}) + 1 << endl;
} else if (op == 4) { // 查询排名为x的元素
x--;
cout << ver.find_by_order(x)->first << endl;
} else if (op == 5) { // 查询元素x的前驱
int idx = ver.order_of_key({x, 1}) - 1; // 无论x存不存在,idx都代表x的位置,需要-1
cout << ver.find_by_order(idx)->first << endl;
} else if (op == 6) { // 查询元素x的后继
int idx = ver.order_of_key( {x, dic[x]}); // 如果x不存在,那么idx就是x的后继
if (ver.find({x, 1}) != ver.end()) idx++; // 如果x存在,那么idx是x的位置,需要+1
cout << ver.find_by_order(idx)->first << endl;
}
}
return 0;
}
其他
留坑。
珂朵莉树 (OD Tree)
这部分的代码来自我的队友 \(\pmb{Hamine}\)。区间赋值的数据结构都可以骗分,在数据随机的情况下,复杂度可以保证,时间复杂度:\(\mathcal O(N\log\log N)\) 。
struct ODT {
struct node {
int l, r;
mutable LL v;
node(int l, int r = -1, LL v = 0) : l(l), r(r), v(v) {}
bool operator<(const node &o) const {
return l < o.l;
}
};
set<node> s;
ODT() {
s.clear();
}
auto split(int pos) {
auto it = s.lower_bound(node(pos));
if (it != s.end() && it->l == pos) return it;
it--;
int l = it->l, r = it->r;
LL v = it->v;
s.erase(it);
s.insert(node(l, pos - 1, v));
return s.insert(node(pos, r, v)).first;
}
void assign(int l, int r, LL x) {
auto itr = split(r + 1), itl = split(l);
s.erase(itl, itr);
s.insert(node(l, r, x));
}
void add(int l, int r, LL x) {
auto itr = split(r + 1), itl = split(l);
for (auto it = itl; it != itr; it++) {
it->v += x;
}
}
LL kth(int l, int r, int k) {
vector<pair<LL, int>> a;
auto itr = split(r + 1), itl = split(l);
for (auto it = itl; it != itr; it++) {
a.push_back(pair<LL, int>(it->v, it->r - it->l + 1));
}
sort(a.begin(), a.end());
for (auto [val, len] : a) {
k -= len;
if (k <= 0) return val;
}
}
LL power(LL a, int b, int mod) {
a %= mod;
LL res = 1;
for (; b; b /= 2, a = a * a % mod) {
if (b % 2) {
res = res * a % mod;
}
}
return res;
}
LL powersum(int l, int r, int x, int mod) {
auto itr = split(r + 1), itl = split(l);
LL ans = 0;
for (auto it = itl; it != itr; it++) {
ans = (ans + power(it->v, x, mod) * (it->r - it->l + 1) % mod) % mod;
}
return ans;
}
};
ST表
用于解决区间可重复贡献问题,需要满足 \(x \text{ 运算符 } x=x\) (如区间最大值:\(\max(x,x)=x\) 、区间 \(\gcd\):\(\gcd(x,x)=x\) 等),但是不支持修改操作。\(\mathcal O(NlogN)\) 预处理,\(\mathcal O(1)\) 查询。
备注:手写 log
函数比直接使用库函数 log2
更快,测试 例题 。
struct ST {
int n;
vector<int> in1, in2, lg;
vector<vector<int>> Max, Min;
ST(int n) : n(n), in1(n + 1), in2(n + 1), lg(n + 1) {
for (int i = 2; i <= n; i++) {
lg[i] = lg[i / 2] + 1;
}
Max.resize(n + 1, vector<int>(lg[n] + 1));
Min.resize(n + 1, vector<int>(lg[n] + 1));
}
void init() {
for (int i = 1; i <= n; i++) {
Max[i][0] = in1[i];
Min[i][0] = in2[i];
}
int k = lg[n];
for (int j = 1; j <= k; j++) {
for (int i = 1; i + (1 << j) - 1 <= n; i++) {
Max[i][j] = max(Max[i][j - 1], Max[i + (1 << (j - 1))][j - 1]);
Min[i][j] = min(Min[i][j - 1], Min[i + (1 << (j - 1))][j - 1]);
}
}
}
int getMax(int l, int r) {
if (l > r) {
swap(l, r);
}
int k = lg[r - l + 1];
return max(Max[l][k], Max[r - (1 << k) + 1][k]);
}
int getMin(int l, int r) {
if (l > r) {
swap(l, r);
}
int k = lg[r - l + 1];
return min(Min[l][k], Min[r - (1 << k) + 1][k]);
}
};
大整数类(高精度计算)
集成了常见的高精度四则运算(包括高精度乘除高精度)与函数,可以与 #define int long long
同时存在。
const int base = 1000000000;
const int base_digits = 9; // 分解为九个数位一个数字
struct bigint {
vector<int> a;
int sign;
bigint() : sign(1) {}
bigint operator-() const {
bigint res = *this;
res.sign = -sign;
return res;
}
bigint(long long v) {
*this = v;
}
bigint(const string &s) {
read(s);
}
void operator=(const bigint &v) {
sign = v.sign;
a = v.a;
}
void operator=(long long v) {
a.clear();
sign = 1;
if (v < 0) sign = -1, v = -v;
for (; v > 0; v = v / base) {
a.push_back(v % base);
}
}
// 基础加减乘除
bigint operator+(const bigint &v) const {
if (sign == v.sign) {
bigint res = v;
for (int i = 0, carry = 0; i < (int)max(a.size(), v.a.size()) || carry; ++i) {
if (i == (int)res.a.size()) {
res.a.push_back(0);
}
res.a[i] += carry + (i < (int)a.size() ? a[i] : 0);
carry = res.a[i] >= base;
if (carry) {
res.a[i] -= base;
}
}
return res;
}
return *this - (-v);
}
bigint operator-(const bigint &v) const {
if (sign == v.sign) {
if (abs() >= v.abs()) {
bigint res = *this;
for (int i = 0, carry = 0; i < (int)v.a.size() || carry; ++i) {
res.a[i] -= carry + (i < (int)v.a.size() ? v.a[i] : 0);
carry = res.a[i] < 0;
if (carry) {
res.a[i] += base;
}
}
res.trim();
return res;
}
return -(v - *this);
}
return *this + (-v);
}
void operator*=(int v) {
check(v);
for (int i = 0, carry = 0; i < (int)a.size() || carry; ++i) {
if (i == (int)a.size()) {
a.push_back(0);
}
long long cur = a[i] * (long long)v + carry;
carry = (int)(cur / base);
a[i] = (int)(cur % base);
}
trim();
}
void operator/=(int v) {
check(v);
for (int i = (int)a.size() - 1, rem = 0; i >= 0; --i) {
long long cur = a[i] + rem * (long long)base;
a[i] = (int)(cur / v);
rem = (int)(cur % v);
}
trim();
}
int operator%(int v) const {
if (v < 0) {
v = -v;
}
int m = 0;
for (int i = a.size() - 1; i >= 0; --i) {
m = (a[i] + m * (long long)base) % v;
}
return m * sign;
}
void operator+=(const bigint &v) {
*this = *this + v;
}
void operator-=(const bigint &v) {
*this = *this - v;
}
bigint operator*(int v) const {
bigint res = *this;
res *= v;
return res;
}
bigint operator/(int v) const {
bigint res = *this;
res /= v;
return res;
}
void operator%=(const int &v) {
*this = *this % v;
}
bool operator<(const bigint &v) const {
if (sign != v.sign) return sign < v.sign;
if (a.size() != v.a.size()) return a.size() * sign < v.a.size() * v.sign;
for (int i = a.size() - 1; i >= 0; i--)
if (a[i] != v.a[i]) return a[i] * sign < v.a[i] * sign;
return false;
}
bool operator>(const bigint &v) const {
return v < *this;
}
bool operator<=(const bigint &v) const {
return !(v < *this);
}
bool operator>=(const bigint &v) const {
return !(*this < v);
}
bool operator==(const bigint &v) const {
return !(*this < v) && !(v < *this);
}
bool operator!=(const bigint &v) const {
return *this < v || v < *this;
}
bigint abs() const {
bigint res = *this;
res.sign *= res.sign;
return res;
}
void check(int v) { // 检查输入的是否为负数
if (v < 0) {
sign = -sign;
v = -v;
}
}
void trim() { // 去除前导零
while (!a.empty() && !a.back()) a.pop_back();
if (a.empty()) sign = 1;
}
bool isZero() const { // 判断是否等于零
return a.empty() || (a.size() == 1 && !a[0]);
}
friend bigint gcd(const bigint &a, const bigint &b) {
return b.isZero() ? a : gcd(b, a % b);
}
friend bigint lcm(const bigint &a, const bigint &b) {
return a / gcd(a, b) * b;
}
void read(const string &s) {
sign = 1;
a.clear();
int pos = 0;
while (pos < (int)s.size() && (s[pos] == '-' || s[pos] == '+')) {
if (s[pos] == '-') sign = -sign;
++pos;
}
for (int i = s.size() - 1; i >= pos; i -= base_digits) {
int x = 0;
for (int j = max(pos, i - base_digits + 1); j <= i; j++) x = x * 10 + s[j] - '0';
a.push_back(x);
}
trim();
}
friend istream &operator>>(istream &stream, bigint &v) {
string s;
stream >> s;
v.read(s);
return stream;
}
friend ostream &operator<<(ostream &stream, const bigint &v) {
if (v.sign == -1) stream << '-';
stream << (v.a.empty() ? 0 : v.a.back());
for (int i = (int)v.a.size() - 2; i >= 0; --i)
stream << setw(base_digits) << setfill('0') << v.a[i];
return stream;
}
/* 大整数乘除大整数部分 */
typedef vector<long long> vll;
bigint operator*(const bigint &v) const { // 大整数乘大整数
vector<int> a6 = convert_base(this->a, base_digits, 6);
vector<int> b6 = convert_base(v.a, base_digits, 6);
vll a(a6.begin(), a6.end());
vll b(b6.begin(), b6.end());
while (a.size() < b.size()) a.push_back(0);
while (b.size() < a.size()) b.push_back(0);
while (a.size() & (a.size() - 1)) a.push_back(0), b.push_back(0);
vll c = karatsubaMultiply(a, b);
bigint res;
res.sign = sign * v.sign;
for (int i = 0, carry = 0; i < (int)c.size(); i++) {
long long cur = c[i] + carry;
res.a.push_back((int)(cur % 1000000));
carry = (int)(cur / 1000000);
}
res.a = convert_base(res.a, 6, base_digits);
res.trim();
return res;
}
friend pair<bigint, bigint> divmod(const bigint &a1,
const bigint &b1) { // 大整数除大整数,同时返回答案与余数
int norm = base / (b1.a.back() + 1);
bigint a = a1.abs() * norm;
bigint b = b1.abs() * norm;
bigint q, r;
q.a.resize(a.a.size());
for (int i = a.a.size() - 1; i >= 0; i--) {
r *= base;
r += a.a[i];
int s1 = r.a.size() <= b.a.size() ? 0 : r.a[b.a.size()];
int s2 = r.a.size() <= b.a.size() - 1 ? 0 : r.a[b.a.size() - 1];
int d = ((long long)base * s1 + s2) / b.a.back();
r -= b * d;
while (r < 0) r += b, --d;
q.a[i] = d;
}
q.sign = a1.sign * b1.sign;
r.sign = a1.sign;
q.trim();
r.trim();
return make_pair(q, r / norm);
}
static vector<int> convert_base(const vector<int> &a, int old_digits, int new_digits) {
vector<long long> p(max(old_digits, new_digits) + 1);
p[0] = 1;
for (int i = 1; i < (int)p.size(); i++) p[i] = p[i - 1] * 10;
vector<int> res;
long long cur = 0;
int cur_digits = 0;
for (int i = 0; i < (int)a.size(); i++) {
cur += a[i] * p[cur_digits];
cur_digits += old_digits;
while (cur_digits >= new_digits) {
res.push_back((int)(cur % p[new_digits]));
cur /= p[new_digits];
cur_digits -= new_digits;
}
}
res.push_back((int)cur);
while (!res.empty() && !res.back()) res.pop_back();
return res;
}
static vll karatsubaMultiply(const vll &a, const vll &b) {
int n = a.size();
vll res(n + n);
if (n <= 32) {
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
res[i + j] += a[i] * b[j];
}
}
return res;
}
int k = n >> 1;
vll a1(a.begin(), a.begin() + k);
vll a2(a.begin() + k, a.end());
vll b1(b.begin(), b.begin() + k);
vll b2(b.begin() + k, b.end());
vll a1b1 = karatsubaMultiply(a1, b1);
vll a2b2 = karatsubaMultiply(a2, b2);
for (int i = 0; i < k; i++) a2[i] += a1[i];
for (int i = 0; i < k; i++) b2[i] += b1[i];
vll r = karatsubaMultiply(a2, b2);
for (int i = 0; i < (int)a1b1.size(); i++) r[i] -= a1b1[i];
for (int i = 0; i < (int)a2b2.size(); i++) r[i] -= a2b2[i];
for (int i = 0; i < (int)r.size(); i++) res[i + k] += r[i];
for (int i = 0; i < (int)a1b1.size(); i++) res[i] += a1b1[i];
for (int i = 0; i < (int)a2b2.size(); i++) res[i + n] += a2b2[i];
return res;
}
void operator*=(const bigint &v) {
*this = *this * v;
}
bigint operator/(const bigint &v) const {
return divmod(*this, v).first;
}
void operator/=(const bigint &v) {
*this = *this / v;
}
bigint operator%(const bigint &v) const {
return divmod(*this, v).second;
}
void operator%=(const bigint &v) {
*this = *this % v;
}
};
KD Tree
这部分的代码来自我的队友 \(\pmb{Hamine}\)。
struct KDT {
constexpr static int N = 1e5 + 10, K = 2;
double alpha = 0.725;
struct node {
int info[K];
int mn[K], mx[K];
} tr[N];
int ls[N], rs[N], siz[N], id[N], d[N];
int idx, rt, cur;
int ans;
KDT() {
rt = 0;
cur = 0;
memset(ls, 0, sizeof ls);
memset(rs, 0, sizeof rs);
memset(d, 0, sizeof d);
}
void apply(int p, int son) {
if (son) {
for (int i = 0; i < K; i++) {
tr[p].mn[i] = min(tr[p].mn[i], tr[son].mn[i]);
tr[p].mx[i] = max(tr[p].mx[i], tr[son].mx[i]);
}
siz[p] += siz[son];
}
}
void maintain(int p) {
for (int i = 0; i < K; i++) {
tr[p].mn[i] = tr[p].info[i];
tr[p].mx[i] = tr[p].info[i];
}
siz[p] = 1;
apply(p, ls[p]);
apply(p, rs[p]);
}
int build(int l, int r) {
if (l > r) return 0;
vector<double> avg(K);
for (int i = 0; i < K; i++) {
for (int j = l; j <= r; j++) {
avg[i] += tr[id[j]].info[i];
}
avg[i] /= (r - l + 1);
}
vector<double> var(K);
for (int i = 0; i < K; i++) {
for (int j = l; j <= r; j++) {
var[i] += (tr[id[j]].info[i] - avg[i]) * (tr[id[j]].info[i] - avg[i]);
}
}
int mid = (l + r) / 2;
int x = max_element(var.begin(), var.end()) - var.begin();
nth_element(id + l, id + mid, id + r + 1, [&](int a, int b) {
return tr[a].info[x] < tr[b].info[x];
});
d[id[mid]] = x;
ls[id[mid]] = build(l, mid - 1);
rs[id[mid]] = build(mid + 1, r);
maintain(id[mid]);
return id[mid];
}
void print(int p) {
if (!p) return;
print(ls[p]);
id[++idx] = p;
print(rs[p]);
}
void rebuild(int &p) {
idx = 0;
print(p);
p = build(1, idx);
}
bool bad(int p) {
return alpha * siz[p] <= max(siz[ls[p]], siz[rs[p]]);
}
void insert(int &p, int cur) {
if (!p) {
p = cur;
maintain(p);
return;
}
if (tr[p].info[d[p]] > tr[cur].info[d[p]]) insert(ls[p], cur);
else insert(rs[p], cur);
maintain(p);
if (bad(p)) rebuild(p);
}
void insert(vector<int> &a) {
cur++;
for (int i = 0; i < K; i++) {
tr[cur].info[i] = a[i];
}
insert(rt, cur);
}
bool out(int p, vector<int> &a) {
for (int i = 0; i < K; i++) {
if (a[i] < tr[p].mn[i]) {
return true;
}
}
return false;
}
bool in(int p, vector<int> &a) {
for (int i = 0; i < K; i++) {
if (a[i] < tr[p].info[i]) {
return false;
}
}
return true;
}
bool all(int p, vector<int> &a) {
for (int i = 0; i < K; i++) {
if (a[i] < tr[p].mx[i]) {
return false;
}
}
return true;
}
void query(int p, vector<int> &a) {
if (!p) return;
if (out(p, a)) return;
if (all(p, a)) {
ans += siz[p];
return;
}
if (in(p, a)) ans++;
query(ls[p], a);
query(rs[p], a);
}
int query(vector<int> &a) {
ans = 0;
query(rt, a);
return ans;
}
};