树状数组

综述

树状数组其实就是一个长得像树的数组
下图为二叉树, 假如每个父亲存的是两个儿子的值(线段树), 便有了树状数组的样子
Alt text
下图为树状数组
Alt text
黑色表示原来的数组(a[i]), 红色是树状数组(c[i]), 每个位置存的是子节点的和
c1 = a[1];
c[2] = a[1] + a[2];
c[3] = a[3];
c[4] = a[1] + a[2] + a[3] + a[4];
c[5] = a[5];
c[6] = a[5] + a[6];
c[7] = a[7];
c[8] = a[1] + a[2] + a[3] + a[4] + a[5] + a[6] + a[7];

c[i] = a[i - 2 ^ k + 1] + a[i - 2 ^ k + 2] … + a[i] // k为i的二进制中从最低位到高位连续零的长度, Eg. 8-> 1000 => k = 3
树状数组图

基本要素

lowbit()

检验数组内有多少个数
lowbit(x) = 2 ^ k; // 取出x的最低位1, k同上
x = 0 => 结果 = 0
x 奇数 => 结果 = 1
x 偶数 => 结果为x中2的最大次方的因子

int lowbit(int x)
{
    return x & (-x);
}

modify()

修改值, 更新过程是查询过程的逆向
Alt text
更新a[1]时, 需要向上更新C[1], C[2], C[4], C[8]

void modify(ll x, ll w)
{
    while (x <= n)
    {
        a[x] += w;
        x += lowbit(x);
    }
}

query()

前缀和

ll query(ll x)
{
	ll s = 0;
	while(x > 0)
	{
		s += c[x];
		x -= lowbit(x);
	}
	return s;
}

例题

单点修改, 区间查询

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll a[1111110];
ll n, q, i, j, t, w, x, tt;

ll lowbit(ll x)
{
    return x & (-x);
}

void modify(ll x, ll w)
{
    while (x <= n)
    {
        a[x] += w;
        x += lowbit(x);
    }
}

ll query(ll x)
{
    ll s = 0;
    while (x > 0)
    {
        s += a[x];
        x -= lowbit(x);
    }
    return s;
}
int main()
{
    cin >> n >> q;
    for (i = 1; i <= n; i++)
    {
        cin >> w;
        modify(i, w);
    }
    while (q--)
    {
        cin >> x >> t >> tt;
        if (x == 1)
            modify(t, tt);
        if (x == 2)
            cout << query(tt) - query(t - 1) << endl;
    }
    return 0;
} 

区间修改,单点查询

要做到区间修改,我们能想到的省时间的做法是什么—差分前缀和
打个比方,也就是说我们要给区间[1,3]的每个数加上4,我们只需要给差分数组的a[1]加上4,给差分数组的a[4]减去4

#include <iostream>
#include <string>

using namespace std;
typedef long long ll;
ll a[1111110];
ll n, q, i, j,k, t, w, x, tt;
ll b, c, d, now, last, num = 1;
ll lowbit(ll x)
{
    return x & (-x);
}

void modify(ll x, ll w)
{
    while (x <= n)
    {
        a[x] += w;
        x += lowbit(x);
    }
}

ll query(ll x)
{
    ll s = 0;
    while (x > 0)
    {
        s += a[x];
        x -= lowbit(x);
    }
    return s;
}

int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);

    cin >> n >> q;
    for (i = 1; i <= n; i++)
    {
        cin >> now;
        modify(i, now - last);
        last = now;
    }
    while (q--)
    {
        cin >> k;
        if (k == 1)
        {
            cin >> b >> c >> d;
            modify(b, d);
            modify(c + 1, -d);
        }
        else if (k == 2)
        {
            cin >> b;
            cout << query(b) << endl;
        }
    }
    return 0;
}

逆序对

猫猫 TOM 和小老鼠 JERRY 最近又较量上了,但是毕竟都是成年人,他们已经不喜欢再玩那种你追我赶的游戏,现在他们喜欢玩统计。

最近,TOM 老猫查阅到一个人类称之为“逆序对”的东西,这东西是这样定义的:对于给定的一段正整数序列,逆序对就是序列中 a i > a j a_i>a_j ai>aj i < j i<j i<j 的有序对。知道这概念后,他们就比赛谁先算出给定的一段正整数序列中逆序对的数目。注意序列中可能有重复数字。

#include <bits/stdc++.h>
#define M 500005
using namespace std;
int a[M], d[M], t[M], n, i;
int lowbit(int x)
{
    return x & -x;
}
void modify(int x) // 把包含这个数的结点都更新
{
    while (x <= n) // 范围
    {
        t[x]++;
        x += lowbit(x);
    }
}
int sum(int x) // 查询1~X有几个数加进去了
{
    int res = 0;
    while (x >= 1)
    {
        res += t[x];
        x -= lowbit(x);
    }
    return res;
}
bool cmp(int x, int y) // 离散化比较函数
{
    if (a[x] == a[y])
        return x > y;   // 避免元素相同
    return a[x] > a[y]; // 按照原序列第几大排列
}
int main()
{
    long long ans = 0;
    cin >> n;
    for (i = 1; i <= n; i++)
        cin >> a[i], d[i] = i;

    sort(d + 1, d + n + 1, cmp); // 离散化

    for (i = 1; i <= n; i++)
    {
        modify(d[i]);         // 把这个数放进去
        ans += sum(d[i] - 1); // 累加
    }
    cout << ans;
    return 0;
}

最大交点数

一个终端是一排 n n n 个连接在一起的相等的线段,有两个终端,一上一下。

有一个数组 a i a_i ai,代表从上面的终端中第 i i i 条线段,到下面的终端中第 a i a_i ai 条线段,有一条连线。

问这些连线最多有几个交点。

只有当某一条线段的上端点大于(没有等于的情况)当前的线段的上端点,而且下端点<= 当前的线段的下端点时两线才会交叉

问题转化为 a[j] ≤ a[i] (i < j) 的方案数, 即求逆序对的个数
每次都是修改一个点 然后求一段区间和

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

int tt;
int n, i, ans, a[200010], b[200010], c[200010];
int lowbit(int x)
{
    return x & (-x);
}

void modify(int x, int w)
{
    while (x <= n)
    {
        c[x] += w;
        x += lowbit(x);
    }
}

int query(int x)
{
    int s = 0;
    while (x > 0)
    {
        s += c[x];
        x -= lowbit(x);
    }
    return s;
}

void solve()
{
    cin >> n;
    memset(c, 0, sizeof(c));
    ans = 0;
    for (i = 1; i <= n; i++)
        b[i] = 0;
    for (i = 1; i <= n; i++)
    {
        cin >> a[i];
        b[a[i]]++;
    }
    for (i = 1; i <= n; i++)
        ans += b[i] * (b[i] - 1) / 2;
    for (i = n; i >= 1; i--)
    {
        modify(a[i], 1);
        if (a[i] > 1)
            ans += query(a[i] - 1);
    }
    cout << ans << endl;
}
signed main()
{
    cin >> tt;
    while (tt--)
        solve();
}

参考

  1. https://www.cnblogs.com/xenny/p/9739600.html
  • 19
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值