树状数组入门


# 基本知识

在这里插入图片描述
1、如果i为奇数,那么C[i] = A[i];
2、C[i]一定包含A[i]; 即A[i] 一定是C[i]的最后一项。
3、i 的二进制,最后一个1所代表的数n,即C[i]包含i项,A[i] - A[i-n+1] 项。
数组下标最后一个1是几,那么该项就包含哪几项就已知了。

int lowbit(int x)
{
	return x-(x&(x-1));
}
int lowbit(int x) // 简洁、核心
{
	return x&(-x);
}

4、构造树状数组C[i] : 更新操作!
实际就是一个普通数组,只是逻辑关系是树状关系。在读数的时候对树状数组C进行更新,读完的同时也就获得了树状数组。
5、区间查询(求和)——单点更新
修改加lowbit,查询减lowbit
利用C数组,求A数组中前i项的和:
比如: i = 7,则前7项和:
sum[7] = A[1] + A[2] +A[3]+ A[4] + A[5] +A[6] + A[7]
sum[7] = C[4] + C[6] + C[7]
组合规律(二进制):
(111) = (100) + (110) + (111)

int sum(int i) // 求区间[1,i]所有元素的和
{
	int ret = 0;
	while(i>0)
	{ 
	   ret += C[i]; // 从右往左区间求和
	   i -= lowbit(i);
	}
	return ret;
}
sum(R) - sum(L-1) // 求区间和

单点更新
从小到大更新

void update(int i,int val)//单点更新(影响多个C元素)
{ 
	while(i<=n)
	{
	   C[i] += val;
	   i  += lowbit(i);
	 }
}

7.区间更新——单点查询
差分数组 d[i] = a[i] - a[i-1]
a[i] 就是对差分数组求前缀和
区间修改:当给区间[l,r]加上x的时候,a[l]与前一个元素a[l-1]的差增加了x,a[r+1]与a[r]的差减少了x。根据d[i]数组的定义,只需要给d[l]加上x,给d[r+1]减去x即可。
区间修改:

d[l] += x;
d[r+1] -= x;

单点更新‘差分’

// 单点更新
void add(int p,int x)
{
	while(p<=n)
	{
	   c[p] += x;
	   p += lowbit(p);
    }
}
// 区间修改
void range_add(int l,int r, int x)
{
 	add(l,x);
 	add(r+1,-x);
 }
 // 单点查询
 int ask(int p)
 {
 	int re = 0;
 	while(p)  re += c[p] , p -= lowbit(p);
 	return re;
 }

8、区间更新—— 区间查询
维护两个数组的前缀和
c1[i] = d[i]
c2[i] = d[i]*i
区间修改:

// 维护两个树状数组
void add(int p,int x)
{
	for(int i=p;i<=n;i+=i&(-i))
	c1[i]+= x ,c2[i]+= x*p;
}
//区间修改
void range_add(int l,int r,int x)
{
	add(l,x);
	add(r+1,-x);
}

区间查询:

int ask(int p ) // 前缀和查询,即查询区间[1,p]的和
{
	int res = 0;
	for(int i=p;i;i-= i&-i)
	 res += (p+1)*c1[i]-c2[i]; // 推导所得
	 return res;
}
int range_ask(int l,int r) // 区间查询
{
	return ask(r) - ask(l-1);
}

第一题

在这里插入图片描述

#include <iostream>

using namespace std;
int lowbit(int x)
{
    return x&(-x);
}
int main()
{
    int n;
    while(1)
    {
        cin >> n;
        if(n==0) break;
        cout << lowbit(n) << endl;
    }
    return 0;
}

第二题

单点修改,区间查讯

#include <iostream>
#include <cstring>
using namespace std;
int n ;
int c[100001];
int lowbit(int x)
{
    return x&(-x);
}
void update(int i,int v)
{
    while(i<=n)
    {
        c[i]+= v;
        i+= lowbit(i);
    }
}
int sum(int i)
{
    int ret =0;
    while(i>0)
    {
        ret += c[i];
        i -=lowbit(i);
    }
    return ret;
}
int main()
{
    int t;
    cin >> t;
    int m;
    int l,r;
    for(int k=1;k<=t;k++)
    {
        cout << "Case "<< k << ":" << endl;
        memset(c,0,sizeof(c));
       cin >> n;
       for(int i=1;i<=n;i++)
       {
           cin >> m;
           update(i,m);
       }
       string s;
       while(1)
       {
           cin >> s;
           if(s=="End")
            break;
           cin >> l >>r;
           if(s=="Query")
           {
               cout << sum(r) - sum(l-1)<< endl;
           }
           else if(s=="Add")
           {
               update(l,r);
           }
           else if(s=="Sub")
           {
               update(l,-r);
           }
       }
    }

    return 0;
}

第三题

问题:
给定n(n<=100000)个正整数,希望对其从小到大排序,如果采用冒泡排序算法,请计算需要进行的交换次数
(每次交换相邻的数组,本质求逆序对的总数)
求逆序对:
在这里插入图片描述
对C[a+1,n]区间求和。统计的时候太慢了,所以利用树状数组。当求a的逆序数时,只需要计算sum(n)-sum(a)即可。

#include <iostream>

using namespace std;
int n;
int vis[100007]={0};
int a[100007];
int c[100007];
int lowbit(int x)
{
    return x&(-x);
}
int sum(int i)
{
    int ret =0;
    while(i>0)
    {
        ret += c[i];
        i -= lowbit(i);
    }
    return ret;
}
int add(int i,int x)
{
    while(i<=n)
    {
        c[i] += x;
        i += lowbit(i);
    }
}
int main()
{
    cin >> n;
    for(int i=0;i<n;i++)
    {
        cin >> a[i];
    }
    int ans=0;
    for(int j=0;j<n;j++)
    {
        ans += j-sum(a[j]);
        add(a[j],1);
    }
    cout << ans << endl;
    return 0;
}

第四题

区间修改,单点查询

#include <iostream>
#include <cstring>
using namespace std;
int c[100001];
int n;
int lowbit(int x)
{
    return x&(-x);
}
void add(int i,int v)
{
    while(i<=n)
    {
        c[i]+= v;
        i+= lowbit(i);
    }
}
void range_add(int l,int r,int x)
{
    add(l,x);
    add(r+1,-x);
}
int sum(int i)
{
    int ret =0;
    while(i>0)
    {
        ret += c[i];
        i -=lowbit(i);
    }
    return ret;
}
int main()
{
    while(1)
    {
        cin >> n;
        if(n==0) break;
        int r,l;
        memset(c,0,sizeof(c));
        for(int i=1; i<=n; i++)
        {
            cin >> l >> r;
            range_add(l,r,1);
        }
        for(int i=1; i<=n; i++)
        {
            if(i==1)
                cout << sum(i);
            else
            cout <<  " " << sum(i);
        }
        cout << endl;
    }
    return 0;
}

第五题

在这里插入图片描述

#include <iostream>
#include <algorithm>
#include <queue>
#include <cstring>

using namespace std;

struct Node
{
    int x;
    int i;
    bool operator< (Node b) const{
        if (x == b.x)
            return i < b.i;
        return x < b.x;
    }
} node;

int a[500005] = {}, n, t;

void update(int x) // 单点更新
{

    for (int i = x; i <= n; i += (i&(-i)))
    {
        a[i] += 1;
    }
}
int ask(int x)  // 单点查询
{
    int ans = 0;
    for (int i = x; i; i -= (i&(-i)))
    {
        ans += a[i];
    }
    return ans;
}
int main()
{
    while (scanf("%d", &n) != EOF && n != 0)
    {
        memset(a, 0, sizeof(int) * (n + 4));
        priority_queue<Node> q;  // 优先队列
        long long ans = 0;
        for (int i = 1; i <= n; i++)
        {
            scanf("%d", &node.x);
            node.i = i;
            q.push(node);
        }
        for (int i = 0; i < n; i++)
        {
            t = q.top().i;  //根据序列号进行修改
            ans += ask(t);
            update(t);
            q.pop();
        }
        printf("%I64d\n", ans);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值