树状数组初探

18 篇文章 0 订阅
11 篇文章 0 订阅

这两天笔者学习了一下树状数组,感觉真的是非常神奇的数据结构。(ORZ发明者)

如果我们要求带修改的序列区间和,可以直接用数组或者用前缀和做,但是数组求和是O(N),前缀和修改是O(N),两者的劣势也非常明显了,这时——树状数组出现了,相当于是一个分段前缀和,这可以形象地比喻为连接数组和前缀和的桥梁:树状数组修改和求和操作都是O(logN)级别的,而且代码短,写法比较固定,所以很少在树状数组中出现错误。

lowbit函数可以说是整个树状数组的灵魂,lowbit(x)=x&(-x),x-lowbit(x)就可以得到x的上一个管理区间,x+lowbit(x)就可以得到下一个管理区间,很神奇

[POJ 2352] Stars

因为坐标是按照纵坐标递增的顺序给定的,所以对于每个星星,只要求出之前有多少点的横坐标小于等于他。每次添加一个星星,将x~maxn的和都加1,表示以后的横坐标大于等于x的星星的等级都能加1.

#include <stdio.h>
#include <algorithm>
#include <string.h>
using namespace std;
#define lowbit(x) ((x)&(-x))

const int maxn=32005;
int n,level[15005],t[maxn],x,y;

int sum(int x){
	int ans=0;
	while (x){
		ans+=t[x];
		x-=lowbit(x);
	}
	return ans;
}

void add(int x){
	while (x<maxn){
		t[x]++;
		x+=lowbit(x);
	}
}

int main(){
	scanf("%d",&n);
	for (int i=1;i<=n;i++){
		scanf("%d%d",&x,&y);
		++x;				//数据坐标从0开始  而树状数组不能处理0的情况 
		level[sum(x)]++;
		add(x);
	}
	for (int i=0;i<n;i++) printf("%d\n",level[i]);
	return 0;
}


[POJ 3321]  Apple Tree

一开始给定的图并不适合树状数组,所以我们要重新编号:每个点有left,right两个从root开始遍历,每访问一个点i就把时间戳time加1,然后i.left=time,遍历完所有子节点以后,i.right=time,表示i的子树里的最后一个节点编号,那么节点i管理的区间就是[i.left,i.right],剩下的就是模板了-_-||

#include <stdio.h>
#include <algorithm>
#include <string.h>
using namespace std;
#define lowbit(x) ((x)&(-x))

const int maxn=200005;
int node[maxn],next[maxn],head[maxn];
int n,m,f[maxn],s[maxn],left[maxn],right[maxn],tot;

void add(int x,int y) {node[++tot]=y;next[tot]=head[x];head[x]=tot;}

int time=0;	//时间戳 
void dfs(int t){
	left[t]=++time;
	for (int i=head[t];i;i=next[i])
		dfs(node[i]);
	right[t]=time;
}

void init(){
	int x,y;
	scanf("%d",&n);
	memset(head,0,sizeof head);
	for (int i=1;i<n;i++){
		scanf("%d %d",&x,&y);
		add(x,y);//add(y,x);
	}
	dfs(1);
	for (int i=1;i<=n;i++)
		s[i]=lowbit(i),f[i]=1;
}

int sum(int x){
	int ans=0;
	for (;x;x-=lowbit(x)) ans+=s[x];
	return ans;
}

void edit(int x,int k){
	for (;x<=n;x+=lowbit(x)) s[x]+=k;
}

int main(){
	init();
	
	int x;char c;
	scanf("%d\n",&m);
	while (m--){
		scanf("%c %d\n",&c,&x);
		if (c=='Q')
			printf("%d\n",sum(right[x])-sum(left[x]-1));
		else{
			f[x]=-f[x];
			edit(left[x],f[x]);
		}
	}
	return 0;
}

[BZOJ 1452] Count

虽然听上去是狠高大上的二维树状数组,不过也就是多了个For循环而已。。。每个颜色都用一个树状数组来表示,直接水过:D

#include <stdio.h>
#include <algorithm>
#include <string.h>
using namespace std;
#define lowbit(x) ((x)&(-x))

const int maxn=305;
int n,m,q,t[maxn][maxn][105],a[maxn][maxn],ans;
int x1,x2,y1,y2,c;

int get(){
	int x=0;char p=getchar();
	while (p<'0' || p>'9') p=getchar();
	while (p>='0' && p<='9') x=x*10+p-'0',p=getchar();
	return  x;
}

void edit(int x,int y,int c,int k){
	for (int i=x;i<=n;i+=lowbit(i))
		for (int j=y;j<=m;j+=lowbit(j))
			t[i][j][c]+=k;
}

int sum(int x,int y,int c){
	int ans=0;
	for (int i=x;i;i-=lowbit(i))
		for (int j=y;j;j-=lowbit(j))
			ans+=t[i][j][c];
	return ans;
}

int main(){
	scanf("%d %d\n",&n,&m);
	memset(t,0,sizeof t);
	for (int i=1;i<=n;i++)
		for (int j=1;j<=m;j++){
			scanf("%d",&a[i][j]);
			edit(i,j,a[i][j],1); 
		}
	scanf("%d\n",&q);char x;
	while (q--){
		if (getchar()=='1'){
			scanf("%d %d",&x1,&y1);
			edit(x1,y1,a[x1][y1],-1);
			scanf("%d\n",&a[x1][y1]);
			edit(x1,y1,a[x1][y1],1);
		}
		else{
			scanf("%d %d %d %d %d\n",&x1,&x2,&y1,&y2,&c);
			ans=sum(x2,y2,c)-sum(x2,y1-1,c)-sum(x1-1,y2,c)+sum(x1-1,y1-1,c);
			printf("%d\n",ans);
		}
	}
	return 0;
}

[HDU 4031]  Attack

一个点的防御失败次数=总防御次数-防御成功次数。

总防御次数用树状数组,区间修改单点查询。数组s存储原来的值(在这题里面可以去掉),数组flag为标记,表示修改量,如果要把区间[a,b]的值加1 ,那么就是[1,b]的标记+1,[1,a-1]的标记-1。

防御成功次数用暴力+优化,记录某个点上次已经访问到哪里和该点已经成功防御次数,注意一开始一次都没防御的时候要特殊处理。

#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
#define lowbit(x) ((x)&(-x))

const int maxn=200005;
int n,m,T,k,tot,flag[maxn];
int time[maxn],def[maxn],att[maxn][2];

void init(){
	memset(att,0,maxn);tot=0;
	memset(flag,0,maxn);
	memset(def,0,maxn);
	memset(time,0,maxn);
	scanf("%d %d %d\n",&n,&m,&k);
}

void edit(int x,int k){
	for (;x;x-=lowbit(x)) flag[x]+=k;
}
int sum(int x){
	int ans=0;
	for (;x<=n;x+=lowbit(x)) ans+=flag[x];
	return ans;
}

void update(int x){
	if (!time[x])								//一次都没防御的情况要特判 
		for (int i=1;i<=tot;i++)
			if (att[i][0]<=x && att[i][1]>=x){
				def[x]++;
				time[x]=i;
				break;
			}
	int p=0;
	for (int i=time[x]+k;i<=tot;i+=k)			//直接跳到下一次可以查找的位置 
		for (;i<=tot;i++)
			if (att[i][0]<=x && att[i][1]>=x){
				def[x]++;p=i;break;
			}
	if (p) time[x]=p;
}

int a,b;char s[20];
void work(){
	scanf("%s",s);
	if (s[0]=='A'){
		scanf("%d %d\n",&a,&b);
		att[++tot][0]=a;att[tot][1]=b;
		edit(a-1,-1);edit(b,1);
	}
	else{
		scanf("%d\n",&a);
		int ans=sum(a);
		update(a);
		ans-=def[a];
		printf("%d\n",ans);
	}
}

int main(){
	scanf("%d",&T);
	for (int i=1;i<=T;i++){
		init();
		printf("Case %d:\n",i);
		while (m--) 
		work();
	}
	return 0;
}

那树状数组的区间修改区间查询呢?像笔者这种蒟蒻还是老老实实滚回去写线段树吧。。。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值