关于树状数组

1.定义:

一种常用的数据结构,虽然树状数组的题线段树都能做,线段树的题树状数组不一定能做,但它代码比线段树短,思路更清晰易懂,并且在某些方面比线段树更好用。可以实现单点修改、单点查询、区间修改、区间查询四个操作

下图可反映树状数组的工作原理


(转载自oi-wiki)

2.常用函数:

(1)lowbit:用来求一个数在二进制下最低位1的位置

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

(2)add:用来做单点修改操作

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

(3)sum:求前缀和,从而求区间和

inline int sum(int x)
{
    int ans=0;
    for(int i=x;i>=1;i-=lowbit(i))
        ans+=c[i];
    return ans;
}

典型例题

例一 P3374 【模板】树状数组1

树状数组的板子题,没什么好说的......

Code

#include <bits/stdc++.h>
using namespace std;
const int MAXN=5e5+5;

inline int read()
{
	register int x=0,f=0;register char ch=getchar();
	while(ch<'0' || ch>'9')f|=ch=='-',ch=getchar();
	while(ch>='0' && ch<='9')x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
	return f?-x:x;
}

int c[MAXN];
int n,m,a[MAXN];

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

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

inline int sum(int x)
{
	int ans=0;
	for(register int i=x;i>=1;i-=lowbit(i))
		ans+=c[i];
	return ans;
}

int main()
{
	n=read(),m=read();
	for(register int i=1;i<=n;i++)
	{
		a[i]=read();
		add(i,a[i]);
	}
	for(register int i=1;i<=m;i++)
	{
		int op=read();
		if(op==1)
		{
			int x=read(),k=read();
			add(x,k);
		} 
		else
		{
			int x=read(),y=read();
			printf("%d\n",sum(y)-sum(x-1));
		}
	}
	return 0;
}

例二 P5057 简单题

这道题其实只要判断数的奇偶就行了,若模2为0则最后是0,反之最后是1,同样用树状数组维护

Code

#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e5+5;

int n,m;
int c[MAXN];

inline int read()
{
	register int x=0,f=0;register char ch=getchar();
	while(ch<'0' || ch>'9')f|=ch=='-',ch=getchar();
	while(ch>='0' && ch<='9')x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
	return f?-x:x;
}

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

inline void add(int x)
{
    for(;x!=0;x-=lowbit(x))
    {
    	c[x]++;
	}
}

inline int ask(int x)
{
    int ans=0;
    for(;x<=n;x+=lowbit(x))
    {
    	ans+=c[x];
	}
    return ans;
}

int main()
{
    n=read(),m=read();
    for(register int i=1;i<=m;i++)
    {
        int op;
        op=read();
        if(op==1)
        {
            int x,y;
            x=read(),y=read();
            add(y),add(x-1);
        }
        else
        {
            int x;
            x=read();
            printf("%d\n",ask(x)%2);
        }
    }
    return 0;
}

例三 P4378 Out of Sorts S

这题就排序+离散化就过了......

Code

#include <bits/stdc++.h>
using namespace std;
const int MAXN=1e5+5;

inline int read()
{
	register int x=0,f=0;register char ch=getchar();
	while(ch<'0' || ch>'9')f|=ch=='-',ch=getchar();
	while(ch>='0' && ch<='9')x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
	return f?-x:x;
}

int n;
int a[MAXN],c[MAXN],p[MAXN];

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

inline void add(int x)
{
	for(;x<=n;x+=lowbit(x))
		c[x]++;
} 

inline int ask(int x)
{
	int ans=0;
	for(;x;x-=lowbit(x))
		ans+=c[x];
	return ans;
}

inline bool cmp(int x,int y)
{
	return a[x]<a[y]||(a[x]==a[y]&&x<y);
}

int main()
{
	n=read();
	for(register int i=1;i<=n;i++)
	{
		scanf("%d",a+i);
		p[i]=i;
	} 
	sort(p+1,p+n+1,cmp);
	for(register int i=1;i<=n;i++)
	{
		a[p[i]]=i;
	}
	int ans=1;
	for(register int i=1;i<=n;i++)
	{
		add(a[i]);
		ans=max(ans,ask(n)-ask(a[i]-1));
	}
	return !printf("%d\n",ans);
	return 0;
}

例四 P2357 守墓人

这道题需要用差分来解决区间修改的问题。
这题有些不大一样,在推出的式子中,需要两个不同的树状数组去维护不同的东西,一个维护差分数组,一个维护差分数组乘下标的值,其他就和普通树状数组数组无异。

Code

#include<bits/stdc++.h>
#define int long long 
using namespace std;
const int MAXN=2e5+5;

int n,f,c[MAXN],a[MAXN];
int v[MAXN];

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

inline void add1(int x,int k)
{
	for(register int i=x;i<=n;i+=lowbit(i))
		c[i]+=k;	
}

inline int sum1(int x)
{
	int ans=0;
	for(register int i=x;i>=1;i-=lowbit(i))
		ans+=c[i];
	return ans;
}

inline void add2(int x,int k)
{
	for(register int i=x;i<=n;i+=lowbit(i))
		v[i]+=k;
}

inline int sum2(int x)
{
	int ans=0;
	for(register int i=x;i>=1;i-=lowbit(i))
		ans+=v[i];
	return ans;
}

signed main()
{
	scanf("%lld%lld",&n,&f);
	for(register int i=1;i<=n;i++)
	{
		scanf("%lld",&a[i]);
		add1(i,i*(a[i]-a[i-1]));
		add2(i,a[i]-a[i-1]);
	}
	for(register int i=1;i<=f;i++)
	{
		int op;
		scanf("%lld",&op);
		if(op==1)
		{
			int l,r,k;
			scanf("%lld%lld%lld",&l,&r,&k);
			add2(l,k);
			add2(r+1,-k);
			add1(l,l*k);
			add1(r+1,-(r+1)*k);
		}
		else if(op==2)
		{
			int k;
			scanf("%lld",&k);
			add2(1,k);
			add2(2,-k);
			add1(1,k);
			add1(2,-k*2); 
		}
		else if(op==3)
		{
			int k;
			scanf("%lld",&k);
			add2(1,-k);
			add2(2,k);
			add1(1,-k);
			add1(2,k*2);
		}
		else if(op==4)
		{
			int l,r;
			scanf("%lld%lld",&l,&r);
			printf("%lld\n",(sum2(r)*(r+1)-sum1(r))-(sum2(l-1)*l-sum1(l-1)));
		}
		else
		{
			printf("%lld\n",v[1]);
		}
	}
	return 0;
}

推荐练习题

T1 P3531 LIT-Letters

这道题要求使前后两个字符串相同,需要交换的次数,其实就是求逆序对的个数。
但是这里因为是字母,所以很明显需要离散化,将字母编号后再求逆序对。

Code

#include <bits/stdc++.h>
#define int long long 
using namespace std;
const int MAXN=1e7+5;

int c[MAXN];
int n;
char a[MAXN],b[MAXN];
int ans;
vector<int>v[30];
int t[30];
int p[MAXN];

inline int read()
{
	register int x=0,f=0;register char ch=getchar();
	while(ch<'0' || ch>'9')f|=ch=='-',ch=getchar();
	while(ch>='0' && ch<='9')x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
	return f?-x:x;
}

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

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

inline int sum(int x)
{
	int ans=0;
	for(register int i=x;i>=1;i-=lowbit(i))
		ans+=c[i];
	return ans;
}

signed main()
{
	n=read();
	scanf("%s%s",a,b);
	memset(t,-1,sizeof(t));
	for(register int i=0;i<=n-1;i++)
	{
		v[b[i]-'A'+1].push_back(i+1);
	}
	for(register int i=0;i<=n-1;i++)
	{
		p[i+1]=v[a[i]-'A'+1][++t[a[i]-'A'+1]];
	}
	for(register int i=1;i<=n;i++)
	{
		add(p[i],1);
		ans+=sum(n)-sum(p[i]);
	}
	printf("%lld",ans);
	return 0;
}
posted @ 2022-05-04 17:04  Code_AC  阅读(37)  评论(0编辑  收藏  举报