树状数组(单点修改+区间查询)

参考资料

树状数组 - OI Wiki (oi-wiki.org)

acwing算法提高课-数据结构-树状数组

AcWing 241. 楼兰图腾 - AcWing

引入 

树状数组是一种支持 单点修改 和 区间求和 的,代码量小的数据结构。O(logn)

前置知识lowbit()

int lowbit(int x) 
{
  return x & -x;
}
// x 的二进制中,最低位的 1 以及后面所有 0 组成的数。
// lowbit(0b01011000) == 0b00001000
//          ~~~~^~~~
// lowbit(0b01110010) == 0b00000010
//          ~~~~~~^~

树状数组

t[x] 表示 a[x-lowbit(x)+1, x] 区间之和。

1、区间右端点为a[x]

2、区间长度为lowbit(x)

3、下标从1开始存储

 区间查询

int getsum(int x) //求a[1]到a[x]之和(前缀和)
{
    int sum = 0;
    for(int i=x;i;i-=lowbit(i))
        sum+=t[i];
    return sum;
}

区间修改

int add(int x,int k) //给单点 a[x] 增加 k
{
	for(int i=x;i<=n;i+=lowbit(i))
		t[i]+=k;
}

模板题

1、楼兰图腾

241. 楼兰图腾 - AcWing题库

P1637 三元上升子序列 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

第二题比较经典,涉及离散化,建议熟练。

//楼兰图腾
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5+10;
typedef long long LL;
int n;
int a[N], t[N]; //t[i]表示树状数组i结点覆盖的范围和
int Lower[N], Greater[N];
//Lower[i]表示左边比第i个位置小的数的个数
//Greater[i]表示左边比第i个位置大的数的个数

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

void add(int x, int k)//将序列中第x个数加上k
{
    for(int i = x; i <= n; i += lowbit(i)) 
		t[i] += k;
}

int ask(int x) //查询序列前x个数的和
{
    int sum = 0;
    for(int i = x; i; i -= lowbit(i))
		sum += t[i];
    return sum;
}

int main()
{
    scanf("%d", &n);
    for(int i = 1; i <= n; i++) 
		scanf("%d", &a[i]);

    //从左向右,依次统计每个位置左边比第i个数y小的数的个数、以及大的数的个数
    for(int i = 1; i <= n; i++)
    {
        int y = a[i]; //第i个数

        //在前面已加入树状数组的所有数中统计在区间[1, y - 1]的数字的出现次数
        Lower[i] = ask(y - 1); 

        //在前面已加入树状数组的所有数中统计在区间[y + 1, n]的数字的出现次数
        Greater[i] = ask(n) - ask(y);

        //将y加入树状数组,即数字y出现1次
        add(y, 1);
    }

    //清空树状数组,从右往左统计每个位置右边比第i个数y小的数的个数、以及大的数的个数
    memset(t, 0, sizeof t);

    LL resA = 0, resV = 0;
    //从右往左统计
    for(int i = n; i >= 1; i--)
    {
        int y = a[i];
        resA += (LL)Lower[i] * ask(y - 1);
        resV += (LL)Greater[i] * (ask(n) - ask(y));

        //将y加入树状数组,即数字y出现1次
        add(y, 1);
    }

    printf("%lld %lld\n", resV, resA);

    return 0;
}

/*这个算法可以解决n个任意数字的情况,对于本题来说,
没必要反着再跑一次,求得位置i前面小于a[i]数字的个数x以后, 
已知一共有a[i] - 1个数小于a[i]
剩下的a[i] - 1 - x个数一定就在位置i的右边了*/
//三元上升子序列
#include <bits/stdc++.h>
using namespace std;
const int N = 3e4+10;//注意如果开1e5+10,add函数中注意循环截止条件为<=N而非n
typedef long long ll;
ll n;
ll a[N],b[N], t[N]; 
ll lower[N];//表示第i个数左边比它小的数的个数 

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

void add(int x, int k)
{
    for(int i = x; i <= n; i += lowbit(i)) //注意截止条件  n为t的数组大小,而非a的数组大小
		t[i] += k;
}

ll ask(int x)
{
    ll sum = 0;
    for(int i = x; i; i -= lowbit(i))
		sum += t[i];
    return sum;
}

int main()
{
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    cin>>n; 
    for(int i = 1; i <= n; i++) 
	{
		cin>>a[i];	
		b[i]=a[i];
	}
	
	sort(a+1,a+1+n);
	int len=unique(a+1,a+1+n)-(a+1);
	for(int i=1;i<=n;i++)
	{
		b[i]=lower_bound(a+1,a+1+len,b[i])-a;
	}
	
    for(int i = 1; i <= n; i++)
    {
        lower[i] = ask(b[i] - 1);
        add(b[i], 1);
    }
    
    memset(t,0,sizeof t);//注意清除 
    
    ll ans=0;
    for(int i = n; i >= 1; i--)
    {
        ll cnt=(n-i)-ask(b[i]);//右边的个数-小于等于它的个数=大于它的个数
        ans+=lower[i]*cnt;
        add(b[i], 1);
    }

    cout<<ans;

    return 0;
}

2、一个简单的整数问题

242. 一个简单的整数问题 - AcWing题库

区间修改(增加一个数),单点查询

思路:求差分数组,对差分数组进行操作,转化为单点修改,区间查询(求和)

#include <bits/stdc++.h>
using namespace std;
const int N = 100009;
int n, m, a[N], t[N];

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

void add(int x,int y) 
{
    for(;x <= n;x += low_bit(x)) t[x] += y;
    return;
}

int ask(int x) 
{
    int ans = a[x];
    for(;x;x -= low_bit(x))
        ans += t[x];
    return ans;
}

int main() 
{
    cin >> n >> m;
    for(int i = 1;i <= n;++ i) cin >> a[i];
    char ch;
    for(int i = 1, l, r, d;i <= m;++ i)
    {
        cin >> ch;
        if (ch == 'C') 
        {
            cin >> l >> r >> d;
            add(l, d); add(r + 1, -d);
        }
        if (ch == 'Q') 
        {
            cin >> d;
            cout << ask(d) << endl;
        }
    }
    return 0;
}

3、逆序对

P1908 逆序对 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

P1774 最接近神的人 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

后者考察的其实就是逆序对。

给出两种不同的离散化方式。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=5e5+10;
int n;
struct node
{
	int idx,num;
};
node a[N];
ll t[N];
int mp[N];//映射 

bool cmp(node x,node y)
{
	if(x.num==y.num)
		return x.idx<y.idx;
	return x.num<y.num;
}

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

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

ll sum(int x)
{
	ll res=0;
	for(int i=x;i;i-=lowbit(i)) res+=t[i];
	return res;
}

int main()
{
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i].num;
		a[i].idx=i;
	}
	 
	sort(a+1,a+1+n,cmp);
	
	for(int i=1;i<=n;i++)//离散化 
	{
		mp[a[i].idx]=i;//将原来的位置映射到排序后的位置 
	}
	 
	ll ans=0;
	for(int i=n;i>=1;i--)//从后往前,计算比当前数小的数 
	{
		ans+=sum(mp[i]-1);
		add(mp[i],1);
	}
	cout<<ans;
	return 0;
}
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=5e5+10;
int n;
ll a[N],b[N];
ll t[N];

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

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

ll sum(int x)
{
	ll res=0;
	for(int i=x;i;i-=lowbit(i)) res+=t[i];
	return res;
}

int main()
{
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
		b[i]=a[i];
	}
	
	sort(a+1,a+1+n);
	int len=unique(a+1,a+1+n)-(a+1);//返回去重后的数组长度 
	for(int i=1;i<=n;i++)
	{
		b[i]=lower_bound(a+1,a+1+len,b[i])-a;//离散化 
	}
	
	ll ans=0;
	for(int i=n;i>=1;i--)
	{
		ans+=sum(b[i]-1);
		add(b[i],1);
	}
	cout<<ans;
	
	return 0;
}

  • 5
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值