数据结构

树状数组

 

板子题:P3374 【模板】树状数组 1 - 洛谷 

#include <bits/stdc++.h>
using namespace std;

#define ll long long
#define qwq ios::sync_with_stdio(0), cin.tie(0), cout.tie(0)
const int N = 2e6 + 10;
ll n, m, a[N], tr[N];
int lowbit(int x) {
    return x & -x;
}
// 单点修改
// a[x] 加上 c
void add(int x, int c) {
    for (int i = x; i <= n; i += lowbit(i))
        tr[i] += c;
}
// 区间查询
// 查询[1, x] 的区间和
ll query(int x) {
    ll ans = 0;
    for (int i = x; i; i -= lowbit(i))
        ans += tr[i];
    return ans;
}
void solve() {
    cin >> n >> m;
    for (int i = 1; i <= n; ++i)
        cin >> a[i], add(i, a[i]);
    while (m--) {
        int op, x, y;
        cin >> op >> x >> y;
        if (op == 1)
            add(x, y);
        else
            cout << query(y) - query(x - 1) << "\n";
    }
}
int main() {
    qwq;
    int T = 1;
    // cin >> T;
    while (T--)
        solve();

    return 0;
}
树状数组

练习:U305748 楼兰图腾 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

#include <bits/stdc++.h>
using namespace std;

#define ll long long
#define qwq ios::sync_with_stdio(0), cin.tie(0), cout.tie(0)
const int N = 2e5 + 10, P = 998244353, INF = 0x3f3f3f3f;
int lowbit(int x) {
    return x & -x;
}
int tr[N], n;
void add(int x, int k) {
    for (int i = x; i <= n; i += lowbit(i))
        tr[i] += k;
}
ll query(int x) {
    ll sum = 0;
    for (int i = x; i; i -= lowbit(i))
        sum += tr[i];
    return sum;
}
void solve() {
    cin >> n;
    vector<int> a(n), l(n), r(n), L(n), R(n);
    // l[i] 表示第 i 个位置左侧有几个 正序对
    // L[i] 表示第 i 个位置左侧有几个 逆序对 /
    for (int i = 0; i < n; ++i)
        cin >> a[i];

    for (int i = 0; i < n; ++i) {
        add(a[i], 1);
        l[i] = query(a[i] - 1);
        L[i] = query(n) - query(a[i]);
    }

    memset(tr, 0, sizeof tr);
    for (int i = n - 1; i >= 0; --i) {
        add(a[i], 1);
        r[i] = query(n) - query(a[i]);
        R[i] = query(a[i] - 1);
    }
    //  V 左侧为逆序对 右侧为正序对
    ll p = 0, q = 0;
    for (int i = 0; i < n; ++i) {
        // cout << L[i] << " " << r[i] << " " << l[i] << " " << R[i] << "\n";
        p += L[i] * r[i];
        q += l[i] * R[i];
    }
    cout << p << " " << q;
}
int main() {
    qwq;
    int T = 1;
    // cin >> T;
    while (T--)
        solve();

    return 0;
}
楼兰图腾

 

线段树

线段树可以在 O( logN ) 的时间复杂度内实现单点修改、区间修改、区间查询(区间求和,求区间最大值,求区间最小值)等操作。

注意点:

  • 线段树的数组一般要开到4 * N;  位运算的写法为 N >> 2
  • 对于懒标记:修改的时候不用用到下面的区间,查询的时候才会用到下面的区间
    • 故每次插入懒标记不用递归到叶子节点

建树

void build(int u, int l, int r) {
    if (l == r) {  // 递归边界
        f[u] = a[l];
        return;
    }
    int mid = l + r >> 1;
    build(u << 1, l, mid);  // 建左儿子
    build(u << 1 | 1, mid + 1, r);
    f[u] = f[u << 1] +
           f[u << 1 | 1];  // 父节点区间和 = 左儿子区间和 + 右儿子区间和
}

单点修改 & 区间查询 (无懒标记)

可以用树状数组解决此类问题

// 当前修改 u 节点, u 节点所对应区间为[l, r] 要给 a[p] 加上 c
void add(int u, int l, int r, int p, int c) {
    f[u] += c;
    if (l == r)
        return;
    int mid = l + r >> 1;
    if (p <= mid)  // p 在左儿子区间
        add(u << 1, l, mid, p, c);
    else
        add(u << 1 | 1, mid + 1, r, p, c);
}
// 当前为 u 节点, u 节点所对应区间为[l, r] 要查询[s, t]区间和
ll query(int u, int l, int r, int s, int t) {
    if (l == s && r == t)
        return f[u];
    int mid = l + r >> 1;
    if (t <= mid)  // 查询区间完全在左侧
        return query(u << 1, l, mid, s, t);
    else if (s > mid)  // 查询区间完全在右侧
        return query(u << 1 | 1, mid + 1, r, s, t);
    else  // 左侧部分 + 右侧部分     *注意修改查询区间的边界
        return query(u << 1, l, mid, s, mid) +
               query(u << 1 | 1, mid + 1, r, mid + 1, t);
}

 板子题:

#include <bits/stdc++.h>
using namespace std;

#define ll long long
#define qwq ios::sync_with_stdio(0), cin.tie(0), cout.tie(0)
const int N = 2e6 + 10;
ll n, m, a[N], tr[N];
int lowbit(int x) {
    return x & -x;
}
// 单点修改
// a[x] 加上 c
void add(int x, int c) {
    for (int i = x; i <= n; i += lowbit(i))
        tr[i] += c;
}
// 区间查询
// 查询[1, x] 的区间和
ll query(int x) {
    ll ans = 0;
    for (int i = x; i; i -= lowbit(i))
        ans += tr[i];
    return ans;
}
void solve() {
    cin >> n >> m;
    for (int i = 1; i <= n; ++i)
        cin >> a[i], add(i, a[i]);
    while (m--) {
        int op, x, y;
        cin >> op >> x >> y;
        if (op == 1)
            add(x, y);
        else
            cout << query(y) - query(x - 1) << "\n";
    }
}
int main() {
    qwq;
    int T = 1;
    // cin >> T;
    while (T--)
        solve();

    return 0;
}
树状数组
#include <bits/stdc++.h>
using namespace std;

#define ll long long
#define qwq ios::sync_with_stdio(0), cin.tie(0), cout.tie(0)
const int N = 2e6 + 10;
ll n, m, a[N], f[N];
// 建树
void build(int u, int l, int r) {
    if (l == r) {  // 递归边界
        f[u] = a[l];
        return;
    }
    int mid = l + r >> 1;
    build(u << 1, l, mid);  // 建左儿子
    build(u << 1 | 1, mid + 1, r);
    f[u] = f[u << 1] +
           f[u << 1 | 1];  // 父节点区间和 = 左儿子区间和 + 右儿子区间和
}
// 单点修改
// 当前修改 u 节点, u 节点所对应区间为[l, r] 要给 a[p] 加上 c
void add(int u, int l, int r, int p, int c) {
    f[u] += c;
    if (l == r)
        return;
    int mid = l + r >> 1;
    if (p <= mid)  // p 在左儿子区间
        add(u << 1, l, mid, p, c);
    else
        add(u << 1 | 1, mid + 1, r, p, c);
}
// 区间查询
// 当前为 u 节点, u 节点所对应区间为[l, r] 要查询[s, t]区间和
ll query(int u, int l, int r, int s, int t) {
    if (l == s && r == t)
        return f[u];
    int mid = l + r >> 1;
    if (t <= mid)  // 查询区间完全在左侧
        return query(u << 1, l, mid, s, t);
    else if (s > mid)  // 查询区间完全在右侧
        return query(u << 1 | 1, mid + 1, r, s, t);
    else  // 左侧部分 + 右侧部分     *注意修改查询区间的边界
        return query(u << 1, l, mid, s, mid) +
               query(u << 1 | 1, mid + 1, r, mid + 1, t);
}
void solve() {
    cin >> n >> m;
    for (int i = 1; i <= n; ++i)
        cin >> a[i];
    build(1, 1, n);  // 1 作为根节点
    while (m--) {
        int op, x, y;
        cin >> op >> x >> y;
        if (op == 1)
            add(1, 1, n, x, y);
        else
            cout << query(1, 1, n, x, y) << "\n";
    }
}
int main() {
    qwq;
    int T = 1;
    // cin >> T;
    while (T--)
        solve();

    return 0;
}
线段树
#include <bits/stdc++.h>
using namespace std;

#define ll long long
#define qwq ios::sync_with_stdio(0), cin.tie(0), cout.tie(0)
const int N = 4e5 + 10;  // 四倍数组大小
ll n, m, a[N], f[N], b[N];
// 建树
void build(int u, int l, int r) {
    if (l == r) {  // 递归边界
        f[u] = a[l];
        return;
    }
    int mid = l + r >> 1;
    build(u << 1, l, mid);  // 建左儿子
    build(u << 1 | 1, mid + 1, r);
    f[u] = f[u << 1] +
           f[u << 1 | 1];  // 父节点区间和 = 左儿子区间和 + 右儿子区间和
}
// 区间修改
// 当前为 u 节点, u 节点所对应区间为[l, r] ,要在[s, t]区间每一个数加上c
void add(int u, int l, int r, int s, int t, ll c) {
    if (l == s && r == t) {
        b[u] += c;  // 修改懒标记 不用修改f[u]
        return;
    }
    f[u] += (t - s + 1) * c;  // 区间对应的增量
    int mid = l + r >> 1;
    if (t <= mid)  // 修改区间完全在左侧
        add(u << 1, l, mid, s, t, c);
    else if (s > mid)  // 修改区间完全在右侧
        add(u << 1 | 1, mid + 1, r, s, t, c);
    else
        add(u << 1, l, mid, s, mid, c),
            add(u << 1 | 1, mid + 1, r, mid + 1, t, c);
}
// 区间查询
// 当前为 u 节点, u 节点所对应区间为[l, r] 要查询[s, t]区间和
ll query(int u, int l, int r, int s, int t, ll sum) {
    sum += b[u];  // sum 记录u 到根节点的标记之和
    if (l == s && r == t)
        return f[u] + sum * (r - l + 1);
    int mid = l + r >> 1;
    if (t <= mid)  // 查询区间完全在左侧
        return query(u << 1, l, mid, s, t, sum);
    else if (s > mid)  // 查询区间完全在右侧
        return query(u << 1 | 1, mid + 1, r, s, t, sum);
    else  // 左侧部分 + 右侧部分     *注意修改查询区间的边界
        return query(u << 1, l, mid, s, mid, sum) +
               query(u << 1 | 1, mid + 1, r, mid + 1, t, sum);
}
void solve() {
    cin >> n >> m;
    for (int i = 1; i <= n; ++i)
        cin >> a[i];
    build(1, 1, n);  // 1 作为根节点
    while (m--) {
        int op, x, y, k;
        cin >> op >> x >> y;
        if (op == 1) {
            cin >> k;
            add(1, 1, n, x, y, k);
        } else {
            cout << query(1, 1, n, x, y, 0) << "\n";
        }
    }
}
int main() {
    qwq;
    int T = 1;
    // cin >> T;
    while (T--)
        solve();

    return 0;
}
线段树

 

 

 

 

 

posted @ 2023-08-04 19:17  匿名人士W  阅读(6)  评论(0编辑  收藏  举报