树状数组总结

发布于:2022-08-02 ⋅ 阅读:(366) ⋅ 点赞:(0)

要求一组数据的 a[l]~a[j] 的和我们可以用前缀和 f[j]-f[i-1]得出,时间复杂度为O(1) 但是如果我们把(l,j)中的一个数修改,例如把a[l]加2 那么如果要维护前缀和数组 要把l~n全部都加2 这样每次修改时间复杂度为O(n) 而如果要把(l,j)的所有数都+2 那么时间复杂度将为 O(n^2) 所以前缀和在面对单点修改和区间修改时效率低下 为了解决数组的单点或者区间更新问题我们引入树状数组

树状数组可以面对的问题方向有:

1.单点修改  (可以单点查询也可以区间查询)

int lowbit(int x){
	return x&(-x);
}
int find(int x){
	int k=0;
	while(x){
		k+=c[x];
		x-=lowbit(x);
	}
	return k;
}
int add(int x,int tmp){
	while(x<=n){
		c[x]+=tmp;
		x+=lowbit(x);
	}
	return 0;
}
int sub(int x,int tmp){
	while(x<=n){
		c[x]-=tmp;
		x+=lowbit(x);
	}
	return 0;
}

2.区间修改 

 2.a 区间修改+单点查询 

 方法: 引入差分数组

例如对于下面这个数组

  • A[] = 1 2 3 5 6 9
  • D[] = 1 1 1 2 1 3

如果我们把[2,5]区间内值加上2,则变成了

  • A[] = 1 4 5 7 8 9
  • D[] = 1 3 1 2 1 1

只需要修改左区间和右区间边界的值就可以完成区间修改     

查询单点时A[i]时A[i] = Σij = 1D[j]  把差分数组D用树状数组表示即可

void update_dot(int idx,int val)//单点更新
{
    while(idx <= n)
    {
        tree[idx] += val;
        idx += (idx & -idx);
    }
}
void update(int L,int R,int delta)//更新左右端点
{
    update_dot(L,delta);
    update_dot(R,-delta);
}
void read(int idx)//单点查询
{
    int res = 0;
    while(idx > 0)
    {
            res += tree[idx];
            idx -= lowbit(idx);
    }
    return res;
}

2.b 区间修改+区间查询

方法:引入差分数组和辅助数组 通过数学结论得出答案 

A[1]+A[2]+...+A[n]

= (D[1]) + (D[1]+D[2]) + ... + (D[1]+D[2]+...+D[n]) 

= n*D[1] + (n-1)*D[2] +... +D[n]

= n * (D[1]+D[2]+...+D[n]) - (0*D[1]+1*D[2]+...+(n-1)*D[n])

引入辅助数组:sum1[i] = D[i],sum2[i] = D[i]*(i-1);

void update_dot(int idx,ll delta)
{
    for(int i = idx ; i <= n ; i += lowbit(i))
    {
        tree1[i] += delta;
        tree2[i] += (idx-1) * delta;
    }
}
void updateRange(int l,int r,ll delta)
{
    update_dot(l,delta);
    update_dot(r + 1,-delta);
}
ll ask(int idx)
{
    ll res = 0;
    for(int i = idx ;i > 0;i -=lowbit(i))
        res += (idx + 1) * tree1[i] - tree2[i];
    return res;
}
ll rangeQuery(int l,int r)
{
    return ask(r) - ask(l - 1);
}

3.树状数组求逆序数 

方法:单点更新时读入a[i]就add(a[i],1),求a[i]前面有多少大于a[i]的数==i-find(a[i]) ,但是当面对a[i]很大并且数据分散时,比如只有两个数一个为1,一个为1e12,那么这样做效率也非常低下,所以有时候需要离散化

离散化:引入b[i]数组,将a[i]中的数据映射到b数组中,再int m=unique(b+1,b+1+n)-(b+1),得到b数组和不重复的数的个数m,再遍历a[i],把a[i]赋值为lower_bound(b+1,b+1+m,a[i])-b,(lower_bound函数返回第一个>=a[i]的数的位置)即a[i]是第几大的数,然后add(a[i],1)即可

C-标枪游戏_第一届河北工业大学程序设计竞赛校赛(同步赛) (nowcoder.com)

#include<bits/stdc++.h>
using namespace std;
const int maxn=100000+5;
#define int long long
int c[maxn],a[maxn],b[maxn];
int lowbit(int x){
	return x&(-x);
}
int find(int x){
	int k=0;
	while(x){
		k+=c[x];
		x-=lowbit(x);
	}
	return  k;
}
int add(int x,int tmp){
	while(x<=maxn){
		c[x]+=tmp;
		x+=lowbit(x);
	}
	return 0;
}
signed main(){
	int n;
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		b[i]=a[i];
	}
	sort(b+1,b+1+n);
	int ans1=0,ans2=0;
	int m=unique(b+1,b+1+n)-(b+1);
	for(int i=1;i<=n;i++){
		a[i]=lower_bound(b+1,b+1+m,a[i])-b;
		add(a[i],1);
	if(i&1){
		ans1+=2*find(a[i])-i;
	}
	else{
		ans2+=2*find(a[i])-i;
	}
	}
	//cout<<ans1<<" "<<ans2<<endl;
	if(ans1==ans2){
		cout<<"hebei shuang king!";
	}
	else if(ans1>ans2){
		cout<<"Calculus is hebei king!";
	}
	else{
		cout<<"huaji is hebei king!";
	}
} 

4.n维前缀和

题目描述:

给定一段长度为n的序列,求序列中有多少对数相加不会产生10进制的进位。
例如:45 + 54 = 99 不会产生进位,合法。
45 +45 = 90 个位上产生进位,不合法。

0<=n<=2e5  0<=a[i]<1e6

思路:

n维前缀和dp[i][j][k][o][p][q]用来表示小于等于i,j,k,o,p,q的数有多少个,用树状数组维护即可

#include<iostream>
using namespace std;

#define int long long
const int N=2e5+5;

int n,a[N],b[10],cnt[1000100];
int t[11][11][11][11][11][11];

void add(int x,int s)
{
    for(int i=1;i<=6;i++) b[i]=x%10,x/=10;
    for(int i=1;i<=6;i++) b[i]++;
    for(int i=b[1];i<=10;i+=i&-i) 
    {
        for(int j=b[2];j<=10;j+=j&-j)
        {
            for(int k=b[3];k<=10;k+=k&-k)
            {
                for(int x=b[4];x<=10;x+=x&-x)
                {
                    for(int y=b[5];y<=10;y+=y&-y)
                    {
                        for(int z=b[6];z<=10;z+=z&-z)
                        {
                            t[i][j][k][x][y][z]+=s;
                        }
                    }
                }
            }
        }
    }
}

int query(int b[])
{
    int ans=0;
    for(int i=b[1];i;i-=i&-i) 
    {
        for(int j=b[2];j;j-=j&-j)
        {
            for(int k=b[3];k;k-=k&-k)
            {
                for(int x=b[4];x;x-=x&-x)
                {
                    for(int y=b[5];y;y-=y&-y)
                    {
                        for(int z=b[6];z;z-=z&-z)
                        {
                            ans+=t[i][j][k][x][y][z];
                        }
                    }
                }
            }
        }
    }
    return ans;
}

signed main()
{
    cin>>n;
    for(int i=1;i<=n;i++) cin>>a[i],cnt[a[i]]++;
    for(int i=0;i<1000000;i++)
     if(cnt[i]) add(i,cnt[i]);
    long long ans=0;
    for(int i=0;i<1000000;i++)
    {
        if(!cnt[i]) continue;
        bool v=1;
        int p=i;
        for(int j=1;j<=6;j++) 
        {
            b[j]=p%10,p/=10;
            if(b[j]>=5) v=0;
            b[j]=10-b[j];
        }
        ans+=1ll*query(b)*cnt[i];
        if(v) ans-=cnt[i];
    }
    cout<<ans/2;
    return 0;
}


网站公告

今日签到

点亮在社区的每一天
去签到