数据结构:树状数组

老规矩,推荐一篇原理讲解清晰的博客!(树状数组(详细分析+应用),看不懂打死我!_树形数组_鲜果维他命的博客-CSDN博客

相对于线段树,树状数组的优点就是代码简洁,容易修改。单缺点就是优点问题只有线段树才能解决,树状数组有一定的局限性。

1,模板

(1)单点修改,区间查询

cpp 复制代码
int lowbit(int x) {
	return x & (-x);
}
int add_dandian(int pos, int k)
{
	for (int i = pos; i <= n; i += lowbit(i)) c[i] += k;
}
int search(int L, int R)
{
	//利用前缀和相减的性质,[L, R] = [1, R] −[1, L − 1]
	int ans = 0;
	for (int i = L - 1; i; i -= lowbit(i)) ans -= c[i];
	for (int i = R; i; i -= lowbit(i)) ans += c[i];
	return 0;
}

(2)区间修改,单点查询

我们需要构造出原数组的差分数组b,然后用树状数组维护b数组即可

对于区间修改的话,我们只需要对差分数组进行操作即可,例如对区间**[L,R]+k,**那么我们只需要更

新差分数组**add(L,k),add(R+1,-k),**这是差分数组的性质.

cpp 复制代码
int lowbit(int x) {
	return x & (-x);
}
void update(int pos, int k)//pos表示修改点的位置,K表示修改的值也即+K操作
{
	for (int i = pos; i <= n; i += lowbit(i)) c[i] += k;
}


void range_add(int L, int R, int k){
    update(L, k);
    update(R + 1, -k);
}

int ask(int pos)//返回区间pos到1的总和
{
	int ans = 0;
	for (int i = pos; i; i -= lowbit(i)) ans += c[i];
	return ans;
}

(3)区间修改,区间查询

cpp 复制代码
void add(ll p, ll x){
    for(int i = p; i <= n; i += i & -i)
        sum1[i] += x, sum2[i] += x * p;
}
void range_add(ll l, ll r, ll x){
    add(l, x), add(r + 1, -x);
}
ll ask(ll p){
    ll res = 0;
    for(int i = p; i; i -= i & -i)
        res += (p + 1) * sum1[i] - sum2[i];
    return res;
}
ll range_ask(ll l, ll r){
    return ask(r) - ask(l - 1);
}

2,题目练习

(1)【模板】树状数组 1 - 洛谷

AC代码

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e6+5;
int n,m,a[N];

int lowbit(int x)
{
	return x&(-x);
}
void add(int pos,int k);
int search(int l,int r);

signed main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		//cin>>a[i];
		int x;
		cin>>x;
		add(i,x);
	}
	for(int i=0;i<m;i++){
		int flag,l,r;
		cin>>flag>>l>>r;
		if(flag==1) add(l,r);
		else cout<<search(l,r)<<endl;
	}
	return 0;
}

void add(int pos,int k)
{
	for(int i=pos;i<=n;i+=lowbit(i)){
		a[i]+=k;
	}
}

int search(int l,int r)
{
	int sum=0;
	for(int i=r;i;i-=lowbit(i)){
		sum+=a[i];
	}
	for(int i=l-1;i;i-=lowbit(i)){
		sum-=a[i];
	}
	return sum;
} 

(2)【模板】树状数组 2 - 洛谷

AC代码

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e6+5;
int n,m,a[N],b[N];

int lowbit(int x)
{
	return x&(-x);
}
void add(int pos,int k);
int find(int pos);

signed main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		add(i,a[i]-a[i-1]);//差分 
	}
	
	for(int i=0;i<m;i++){
		int flag,l,r,k;
		cin>>flag;
		if(flag==1){
			cin>>l>>r>>k;
			add(l,k);
			add(r+1,-k);
		}
		else{
			cin>>l;
			cout<<find(l)<<endl;
		}
	}
	return 0;
}

void add(int pos,int k)
{
	for(int i=pos;i<=n;i+=lowbit(i)) b[i]+=k;
	return;
}

int find(int pos)
{
	int sum=0;
	for(int i=pos;i;i-=lowbit(i)) sum+=b[i];
	return sum;
}

(3)逆序队 逆序对 - 洛谷

AC代码

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
int n, a[5000001], b[5000001];
long long ans;
inline void msort(int l, int r)//归并排序
{
	int mid = (l + r) / 2;//取中间 
	if(l == r)//若l == r了,就代表这个子序列就只剩1个元素了,需要返回 
	{
		return;
	}
	else
	{
		msort(l, mid);//分成l和中间一段,中间 + 1和r一段(二分) 
		msort(mid + 1, r);
	}
	int i = l;//i从l开始,到mid,因为现在排序的是l ~ r的区间且要二分合并 
	int j = mid + 1;//j从mid + 1开始,到r原因同上
	int t = l;//数组b的下标,数组b存的是l ~ r区间排完序的值 
	while(i <= mid && j <= r)//同上i,j的解释 
	{
		if(a[i] > a[j])//如果前面的元素比后面大(l ~ mid中的元素 > mid + 1 ~ r中的元素)(逆序对出现!!!) 
		{ 
			ans += mid - i + 1;//由于l ~ mid和mid + 1 ~ r都是有序序列所以一旦l ~ mid中的元素 > mid + 1 ~ r中的元素而又因为第i个元素 < i + 1 ~ mid那么i + 1 ~ mid的元素都 > 第j个元素。所以+的元素个数就是i ~ mid的元素个数,及mid - i + 1(归并排序里没有这句话,求逆序对里有) 
			b[t++] = a[j++];//第j个元素比i ~ mid的元素都小,那么第j个元素是目前最小的了,就放进b数组里 
			//++j;//下一个元素(mid + 1 ~ r的元素小,所以加第j个元素) 
		}
		else
		{
			b[t++] = a[i++];//i小,存a[i] 
			//++i;//同理 
		}
	}
	while(i <= mid)//把剩的元素(因为较大所以在上面没选) 
	{
		b[t++] = a[i++];//存进去 
		//++i; 
	}
	while(j <= r)//同理 
	{
		b[t++] = a[j++];
		//++j;
	}
	for(int i = l; i <= r; ++i)//把有序序列b赋值到a里 
	{
		a[i] = b[i];
	}
	return;
}
int main()
{
	scanf("%d", &n);
	for(int i = 1; i <= n; ++i)
	{
		scanf("%d", &a[i]);
	}
	msort(1, n);//一开始序列是1 ~ n 
	printf("%lld", ans);
	return 0;
}

(4)康托展开 【模板】康托展开 - 洛谷

AC代码

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e6 + 10;
const int mod = 998244353;
int a[N], w[N]={1,1},tr[N], n,ans;

int lowbit(int x) {
	return x & (-x);
}
void update(int pos, int k) {
	for (int i = pos; i <= n; i += lowbit(i)) tr[i] += k;
	return;
}
int query(int pos)
{
	int sum = 0;
	for (int i = pos; i; i -= lowbit(i)) sum+=tr[i];
	return sum;
}


signed main()
{
	cin >> n;
	for (int i = 1; i <= n; i++) {//求阶乘
		w[i] = (i * w[i - 1]) % mod;
		update(i, 1);
	}
	for (int i = 1; i <= n; i++) {
		cin >> a[i];
		ans = (ans + ((query(a[i]) - 1) * w[n-i]) % mod) % mod;
		update(a[i], -1);//减1后就变成0了
	}
	cout << ans+1 << endl;
	return 0;
}

(5)二维树状数组 上帝造题的七分钟 - 洛谷

思想:和二维前缀和的思路很相似

AC代码

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include <bits/stdc++.h>
using namespace std;
const int N = 3000;
int board[N][N];
int n, m;

// 定义树状数组结构(树状数组三件套)
struct BIT
{
    int tr[N][N];  // 树状数组
    int lowbit(int x) {
        return x & (-x);  // 返回 x 的最低位的 1 所在位置
    }
    
    // 在坐标 (x, y) 处添加值 k
    void add(int x, int y, int k)
    {
        for (int i = x; i <= n; i += lowbit(i)) {
            for (int j = y; j <= m; j += lowbit(j)) {
                tr[i][j] += k;  // 在 (i, j) 处加上值 k
            }
        }
    }
    
    // 查询坐标 (x, y) 处的前缀和
    int query(int x, int y)
    {
        int sum = 0;
        for (int i = x; i; i -= lowbit(i)) {
            for (int j = y; j; j -= lowbit(j)) {
                sum += tr[i][j];  // 查询 (1, 1) 到 (x, y) 的前缀和
            }
        }
        return sum;
    }
} A, Ai, Aj, Aij;  // 定义四个不同的树状数组

void Add(int x, int y, int k);
int Ans(int x, int y);

int main()
{
    char ch;
    cin >> ch >> n >> m;  // 读取矩阵大小
    while (cin >> ch) {
        int x1, x2, y1, y2;
        cin >> x1 >> y1 >> x2 >> y2;
        if (ch == 'L') {
            int num;
            cin >> num;
            Add(x1, y1, num);  // 在指定区域添加值 num
            Add(x1, y2 + 1, -num);
            Add(x2 + 1, y1, -num);
            Add(x2 + 1, y2 + 1, num);
        }
        else {
            cout << Ans(x2, y2) - Ans(x1 - 1, y2) - Ans(x2, y1 - 1) + Ans(x1 - 1, y1 - 1) << endl;
            // 查询并输出给定矩形区域的和
        }
    }
    return 0;
}

// 计算 (x, y) 处的结果
int Ans(int x, int y)
{
    return A.query(x, y) * (x * y + x + y + 1) - Ai.query(x, y) * (y + 1) - Aj.query(x, y) * (x + 1) + Aij.query(x, y);
}

// 在坐标 (x, y) 处添加值 num
void Add(int x, int y, int num)
{
    A.add(x, y, num);
    Ai.add(x, y, num * x);
    Aj.add(x, y, num * y);
    Aij.add(x, y, num * x * y);
}
相关推荐
杰九8 分钟前
【算法题】46. 全排列-力扣(LeetCode)
算法·leetcode·深度优先·剪枝
manba_16 分钟前
leetcode-560. 和为 K 的子数组
数据结构·算法·leetcode
liuyang-neu17 分钟前
力扣 11.盛最多水的容器
算法·leetcode·职场和发展
忍界英雄25 分钟前
LeetCode:2398. 预算内的最多机器人数目 双指针+单调队列,时间复杂度O(n)
算法·leetcode·机器人
Kenneth風车26 分钟前
【机器学习(五)】分类和回归任务-AdaBoost算法-Sentosa_DSML社区版
人工智能·算法·低代码·机器学习·数据分析
C7211BA44 分钟前
使用knn算法对iris数据集进行分类
算法·分类·数据挖掘
Tisfy1 小时前
LeetCode 2398.预算内的最多机器人数目:滑动窗口+单调队列——思路清晰的一篇题解
算法·leetcode·机器人·题解·滑动窗口
程序猿练习生1 小时前
C++速通LeetCode简单第18题-杨辉三角(全网唯一递归法)
c++·算法·leetcode
Huazzi.1 小时前
算法题解:斐波那契数列(C语言)
c语言·开发语言·算法
汉字萌萌哒1 小时前
【2022 CCF 非专业级别软件能力认证第一轮(CSP-J1)入门级 C++语言试题及解析】
数据结构·c++·算法