1. 树状数组
1.1. 作用
在O(logn)时间内:
1.求修改数组内单点数据
2.查询区间和
1.2. 板子
template <class T> struct fenwick_tree {
using U = internal::to_unsigned_t<T>;
public:
fenwick_tree() : _n(0) {}
explicit fenwick_tree(int n) : _n(n), data(n) {}
void add(int p, T x) {
assert(0 <= p && p < _n);
p++;
while (p <= _n) {
data[p - 1] += U(x);
p += p & -p;
}
}
T sum(int l, int r) {
assert(0 <= l && l <= r && r <= _n);
return sum(r) - sum(l);
}
private:
int _n;
std::vector<U> data;
U sum(int r) {
U s = 0;
while (r > 0) {
s += data[r - 1];
r -= r & -r;
}
return s;
}
};
1.3. 使用实例
int main() {
int n, q;
scanf("%d %d", &n, &q);
fenwick_tree<long long> fw(n);
//相当于构造了个全是0的树状数组
for (int i = 0; i < n; i++) {
int a;
scanf("%d", &a);
fw.add(i, a);
//单点修改,将数组第i个值改为a
}
for (int i = 0; i < q; i++) {
int t;
scanf("%d", &t);
if (t == 0) {
int p, x;
scanf("%d %d", &p, &x);
fw.add(p, x);
} else {
int l, r;
scanf("%d %d", &l, &r);
printf("%lld\n", fw.sum(l, r));
// 调用sum函数,求l到r的和
}
}
}
1.4. 树状数组(QQ版)板子
template <class T>
class Array_tree {
public:
Array_tree() {}
Array_tree(int n) { this->n = n, tree = vector<T>(n + 1); }
void add(int id, T key) {
for (int i = id; i <= n; i += lowbit(i)) tree[i] += key;
}
T get_sum(int id) {
T sum = 0;
for (int i = id; i; i -= lowbit(i)) sum += tree[i];
return sum;
}
T get_sum(int l, int r) { return get_sum(r) - get_sum(l - 1); }
private:
int n;
vector<T> tree;
int lowbit(int x) { return x & -x; }
};
1.5. 进阶用法
1.5.1. 权值树状数组
类似于权值线段树,但是常数比较小
1.5.2. 区间修改
1.5.2.1. 单点查询
使用树状数组来维护,单点增量,也就是维护一个差分数组,查询时只需要返回该位置原数加上增量即可
1.5.2.2. 区间查询
https://www.acwing.com/solution/content/44886/
2. 并查集
2.1. 作用
1.merge函数:合并两个并查集
2.same函数:判断两个点是否在同一个集合内
3.leader函数:返回这个这个点所在集合顶点函数
4.size函数:返回该点所在集合
2.2. 板子
struct dsu {
public:
dsu() : _n(0) {}
explicit dsu(int n) : _n(n), parent_or_size(n, -1) {}
int merge(int a, int b) {
assert(0 <= a && a < _n);
assert(0 <= b && b < _n);
int x = leader(a), y = leader(b);
if (x == y) return x;
if (-parent_or_size[x] < -parent_or_size[y]) std::swap(x, y);
parent_or_size[x] += parent_or_size[y];
parent_or_size[y] = x;
return x;
}
bool same(int a, int b) {
assert(0 <= a && a < _n);
assert(0 <= b && b < _n);
return leader(a) == leader(b);
}
int leader(int a) {
assert(0 <= a && a < _n);
if (parent_or_size[a] < 0) return a;
return parent_or_size[a] = leader(parent_or_size[a]);
}
int size(int a) {
assert(0 <= a && a < _n);
return -parent_or_size[leader(a)];
}
std::vector<std::vector<int>> groups() {
std::vector<int> leader_buf(_n), group_size(_n);
for (int i = 0; i < _n; i++) {
leader_buf[i] = leader(i);
group_size[leader_buf[i]]++;
}
std::vector<std::vector<int>> result(_n);
for (int i = 0; i < _n; i++) {
result[i].reserve(group_size[i]);
}
for (int i = 0; i < _n; i++) {
result[leader_buf[i]].push_back(i);
}
result.erase(
std::remove_if(result.begin(), result.end(),
[&](const std::vector<int>& v) { return v.empty(); }),
result.end());
return result;
}
private:
int _n;
// root node: -1 * component size
// otherwise: parent
std::vector<int> parent_or_size;
};
2.3. 使用实例
int solve() {
int n, q;
scanf("%d %d", &n, &q);
dsu d(n);
for (int i = 0; i < q; i++) {
int u, v;
cin >> u >> v;
d.merge(u, v);//合并
}
//判断是否在同一个集合
if (d.same(1, 2))
cout << "YES\n";
else
cout << "NO\n";
//返回父节点
cout << d.leader(1) << endl;
//返回集合大小
cout << d.size(1) << endl;
return 0;
}
输入
5 3
1 2
0 4
1 3
输出
NO
0
2
2.4. 并查集(QQ版)板子
class Dsu {
//下标从1开始
public:
vll fa, num;
Dsu(int n) { fa = vll(n + 1), num = vll(n + 1, 1); }
int find(int x) {
if (!fa[x]) return x;
return fa[x] = find(fa[x]);
}
// 合并
bool Dunion(int p, int q) {
int v = find(p), u = find(q);
if (v == u) return 0;
fa[u] = v;
num[v] += num[u];
num[u] = num[v];
return 1;
}
ll num(int x) { return num[find(x)]; }
};
3. 线段树
3.1. 作用
对于一个满足下列性质的数组可生成线段树:(假设线段树需要维护的操作为 op(),比如维护区间和的op(a, b)为a + b)
1.满足结合率 即 op(op(a, b), c) = op(a, op(b, c))
2.需要一个幺元e 满足 op(e,a) = op(a, e),即e对任何数操作都为任何数, 比如0加任何数都为任何数
3.2. 板子(无lazy版)
namespace internal {
int ceil_pow2(int n) {
int x = 0;
while ((1U << x) < (unsigned int)(n)) x++;
return x;
}
constexpr int bsf_constexpr(unsigned int n) {
int x = 0;
while (!(n & (1 << x))) x++;
return x;
}
int bsf(unsigned int n) {
#ifdef _MSC_VER
unsigned long index;
_BitScanForward(&index, n);
return index;
#else
return __builtin_ctz(n);
#endif
}
} // namespace internal
template <class S, S (*op)(S, S), S (*e)()>
struct segtree {
public:
segtree() : segtree(0) {}
explicit segtree(int n) : segtree(std::vector<S>(n, e())) {}
explicit segtree(const std::vector<S>& v) : _n(int(v.size())) {
log = internal::ceil_pow2(_n);
size = 1 << log;
d = std::vector<S>(2 * size, e());
for (int i = 0; i < _n; i++) d[size + i] = v[i];
for (int i = size - 1; i >= 1; i--) {
update(i);
}
}
void set(int p, S x) {
assert(0 <= p && p < _n);
p += size;
d[p] = x;
for (int i = 1; i <= log; i++) update(p >> i);
}
S get(int p) const {
assert(0 <= p && p < _n);
return d[p + size];
}
S prod(int l, int r) const {
assert(0 <= l && l <= r && r <= _n);
S sml = e(), smr = e();
l += size;
r += size;
while (l < r) {
if (l & 1) sml = op(sml, d[l++]);
if (r & 1) smr = op(d[--r], smr);
l >>= 1;
r >>= 1;
}
return op(sml, smr);
}
S all_prod() const { return d[1]; }
template <bool (*f)(S)>
int max_right(int l) const {
return max_right(l, [](S x) { return f(x); });
}
template <class F>
int max_right(int l, F f) const {
assert(0 <= l && l <= _n);
assert(f(e()));
if (l == _n) return _n;
l += size;
S sm = e();
do {
while (l % 2 == 0) l >>= 1;
if (!f(op(sm, d[l]))) {
while (l < size) {
l = (2 * l);
if (f(op(sm, d[l]))) {
sm = op(sm, d[l]);
l++;
}
}
return l - size;
}
sm = op(sm, d[l]);
l++;
} while ((l & -l) != l);
return _n;
}
template <bool (*f)(S)>
int min_left(int r) const {
return min_left(r, [](S x) { return f(x); });
}
template <class F>
int min_left(int r, F f) const {
assert(0 <= r && r <= _n);
assert(f(e()));
if (r == 0) return 0;
r += size;
S sm = e();
do {
r--;
while (r > 1 && (r % 2)) r >>= 1;
if (!f(op(d[r], sm))) {
while (r < size) {
r = (2 * r + 1);
if (f(op(d[r], sm))) {
sm = op(d[r], sm);
r--;
}
}
return r + 1 - size;
}
sm = op(d[r], sm);
} while ((r & -r) != r);
return 0;
}
private:
int _n, size, log;
std::vector<S> d;
void update(int k) { d[k] = op(d[2 * k], d[2 * k + 1]); }
};
3.3. 使用实例
ll op(ll a, ll b) { return a + b; };
ll e() { return 0; };
void solve() {
cin >> n >> m;
vll a(n + 1);
for (int i = 1; i <= n; i++) cin >> a[i];
segtree<ll, op, e> seg(a);
while (m--) {
ll t, x, y;
cin >> t >> x >> y;
if (t == 1)
seg.set(x, seg.get(x) + y);
else
cout << seg.prod(x, y + 1) << endl;
}
return;
}
输入:
10 2
1 2 3 4 5 6 7 8 9 10
1 1 9
2 1 10
输出:
64
3.4. 线段树(QQ版)
该版方便魔改
class SGM_Tree {
public:
class point {
public:
ll sum, maxi, mini;
};
vll a, lazy;
int n;
ll sum, maxi, mini;
vector<point> tree;
SGM_Tree() {}
SGM_Tree(int n, vi v) {
// a下标默认从1开始,只需开n个点,不需要n + 1
this->n = n;
lazy = vll(n * 4 + 1);
a.push_back(0);
for (int i = 1; i <= n; i++) a.push_back(v[i]);
tree = vector<point>(4 * n + 1), build(1, n, 1);
}
SGM_Tree(int n, vll v) {
// a下标默认从1开始,只需开n个点,不需要n + 1
this->n = n;
lazy = vll(n * 4 + 1);
a.push_back(0);
for (int i = 1; i <= n; i++) a.push_back(v[i]);
tree = vector<point>(4 * n + 1), build(1, n, 1);
}
SGM_Tree(int n, int* v) {
// a下标默认从1开始,只需开n个点,不需要n + 1
this->n = n;
lazy = vll(n * 4 + 1);
a.push_back(0);
for (int i = 1; i <= n; i++) a.push_back(v[i]);
tree = vector<point>(4 * n + 1), build(1, n, 1);
}
void push_up(int k) {
int l = k * 2, r = k * 2 + 1;
tree[k].sum = tree[l].sum + tree[r].sum;
tree[k].maxi = max(tree[l].maxi, tree[r].maxi);
tree[k].mini = min(tree[l].mini, tree[r].mini);
}
void push_down(int l, int r, int k) {
if (lazy[k]) {
int mid = l + r >> 1;
lazy[k * 2] += lazy[k];
lazy[k * 2 + 1] += lazy[k];
tree[k * 2].sum += lazy[k] * (mid - l + 1);
tree[k * 2 + 1].sum += lazy[k] * (r - mid);
tree[k * 2].maxi += lazy[k];
tree[k * 2 + 1].maxi += lazy[k];
tree[k * 2].mini += lazy[k];
tree[k * 2 + 1].mini += lazy[k];
lazy[k] = 0;
}
}
void get_updata(int l, int r, int k, ll value) {
tree[k].sum += value * (r - l + 1);
tree[k].maxi += value;
tree[k].mini += value;
lazy[k] += value;
}
void get(int k) {
sum += tree[k].sum;
maxi = max(maxi, tree[k].maxi);
mini = min(mini, tree[k].mini);
}
void build(int l, int r, int k) {
if (l == r) {
tree[k].maxi = tree[k].mini = tree[k].sum = a[l];
return;
}
int mid = l + r >> 1;
build(l, mid, k * 2);
build(mid + 1, r, k * 2 + 1);
push_up(k);
}
void updata(int l, int r, int L, int R, int k, ll value) {
if (L <= l && r <= R) {
get_updata(l, r, k, value);
return;
}
push_down(l, r, k);
int mid = l + r >> 1;
if (L <= mid) updata(l, mid, L, R, k * 2, value);
if (R > mid) updata(mid + 1, r, L, R, k * 2 + 1, value);
push_up(k);
}
void query(int l, int r, int L, int R, int k) {
if (L <= l && r <= R) {
get(k);
return;
}
push_down(l, r, k);
int mid = l + r >> 1;
if (mid >= L) query(l, mid, L, R, 2 * k);
if (mid < R) query(mid + 1, r, L, R, 2 * k + 1);
}
ll get_sum(int L, int R) {
sum = 0;
query(1, n, L, R, 1);
return sum;
}
ll get_max(int L, int R) {
maxi = -inf;
query(1, n, L, R, 1);
return maxi;
}
ll get_min(int L, int R) {
mini = inf;
query(1, n, L, R, 1);
return mini;
}
};
我的评价是,atc的真难用
3.5. 进阶用法
3.5.1. 权值线段树
https://blog.csdn.net/yanweiqi1754989931/article/details/117380913
简单来说就是维护一个数组,在一个值域内的数有多少个。
3.5.2. 区间维护gcd
3.5.3. 求逆序对
用权值线段树,首先建一个空树,然后每次加点,询问不符合该值大小的数量多少,总和便是逆序对数量。
4. st表
4.1. 作用
在O(logn)的时间复杂度将一个数组区间最值或gcd求出
每次查询O(1)
4.2. 板子
class st_map {
public:
st_map() {}
st_map(vll v) {
this->n = v.size(), this->a = v;
this->st = vector<array<ll, 31>>(n + 1);
st_init();
}
int query(int l, int r) {
int len = r - l + 1;
int k = log(len) / log(2);
return max(st[l][k], st[r - (1 << k) + 1][k]);
}
private:
int n;
vll a;
vector<array<ll, 31>> st;
void st_init() {
for (int j = 0; j <= 17; j++) {
for (int i = 1; i + (1 << j) - 1 <= n; i++) {
if (j == 0)
st[i][j] = a[i];
else
st[i][j] = max(st[i][j - 1], st[i + (1 << j - 1)][j - 1]);
}
}
}
};
5. 主席树(可持久线段树)
5.1. 作用
和线段树差不多,但是可以保存下所有的历史版本
5.2. 板子
template <class T>
class HJT_tree {
//处理数据默认下标从1开始
public:
//构造函数
HJT_tree() {}
HJT_tree(vector<T> v) {
base = v, this->n = base.size() - 1;
tree = vector<node>(n * 32), root.push_back(build(1, n));
}
void updata(int v, int x, T value) {
//插入函数(版本,修改位置,修改值)
root.push_back(insert(root[v], 1, n, x, value));
}
T query(int v, int x) {
//查询函数(版本,查询位置)
return get_se(root[v], 1, n, x, x);
}
T query(int v, int l, int r) {
//查询函数(版本,查询区间)
return get_se(root[v], 1, n, l, r);
}
private:
vi root;
vector<T> base;
int n, idx = 0;
struct node {
int l, r;
T data;
};
vector<node> tree;
void pushup(int q) { tree[q].data = op(tree[q].l, tree[q].r); }
T op(int l, int r) { return max(tree[l].data, tree[r].data); }
T e() { return -inf; }
int build(int l, int r) {
int now = ++idx, mid = l + r >> 1;
if (l != r)
tree[now].l = build(l, mid), tree[now].r = build(mid + 1, r), pushup(now);
return now;
}
int insert(int old, int l, int r, int x, int value) {
int now = ++idx, mid = l + r >> 1;
tree[now] = tree[old];
if (l == r)
tree[now].data = value;
else {
if (x <= mid)
tree[now].l = insert(tree[old].l, l, mid, x, value);
else
tree[now].r = insert(tree[old].r, mid + 1, r, x, value);
pushup(now);
}
return now;
}
T get_se(int v, int l, int r, int L, int R) {
if (L <= l && r <= R) return tree[v].data;
ll mid = l + r >> 1;
T res = e();
if (L <= mid) res = max(res, get_se(tree[v].l, l, mid, L, R));
if (R > mid) res = max(res, get_se(tree[v].r, mid + 1, r, L, R));
return res;
}
};
5.3. 使用实例
https://www.luogu.com.cn/record/84259274
此题卡输入,但是板子还是一样的