回忆树状数组,发现自己原来写的树状数组都有点像线段树,有点虚,于是重新看了一下课件,发现原来的认为的树状数组是用数组实现的线段树。所以就只有重新看课件从基础做起。
敌兵布阵 (HDU-1166)
基础的树状数组题,但是为了复习将整个思路写一下。
首先树状数组其实是运用了二进制的思想,例如
(C是树状数组,A是原数组)
C1=A1
C2=A1+A2
C3=A3
C4=A1+A2+A3+A4
C5=A5
C6=A5+A6
……
这里有一个有趣的性质:设节点编号为x,那么这个节点管辖的区间为2^k(刚好为x的二进制中最后一个1表示的值)这个性质很重要,是更改数组里的值和区间求和的关键
而找这个k可以用位运算,x&(-x)来迅速地得到。
因此我们更改一个元素的值,就是把管辖它的树状数组同时更改。而查询区间,例如区间i,j,就是用j管辖的区间减去i-1管辖的区间。
复习了以上的基本操作这道题就非常简单了,只需要把上述的东西实现就可以。
代码如下:
#include<cstdio>
#include<cstring>
#include<algorithm>
#define MAXN 50000
#define lowbit(x) (x&(-x))
using namespace std;
int T,n;
int tree[MAXN+5];
void add(int x,int va)
{
while(x<=n)
{
tree[x]+=va;
x+=lowbit(x);
}
}
int sum(int x)
{
int now=0;
while(x>0)
{
now+=tree[x];
x-=lowbit(x);
}
return now;
}
int main()
{
scanf("%d",&T);
for(int k=1;k<=T;k++)
{
memset(tree,0,sizeof tree);
scanf("%d",&n);
printf("Case %d:\n",k);
for(int i=1;i<=n;i++)
{
int t;
scanf("%d",&t);
add(i,t);
}
while(1)
{
char s[10];
scanf("%s",s);
if(s[0]=='E')
break;
if(s[0]=='A')
{
int x,y;
scanf("%d%d",&x,&y);
add(x,y);
}
else if(s[0]=='S')
{
int x,y;
scanf("%d%d",&x,&y);
add(x,-y);
}
else
{
int x,y;
scanf("%d%d",&x,&y);
printf("%d\n",sum(y)-sum(x-1));
}
}
}
}
Ultra-QuickSort (POJ - 2299)
题目大意:每次交换相邻两个位置的数,求将给定序列操作到升序序列的最少操作次数。
首先这道题需要离散化,它的n特别小但是每个元素的值却又非常大,离散化后进行树状数组的操作。实际上就是统计这个数前面有多少个数比它大,求这个值得过程中我们就可以用树状数组来进行操作。值得注意的是最后的答案可能大于int,所以要用longlong。(因为这个我错了三次!!!)
代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
#define lowbit(x) (x&(-x))
using namespace std;
int n;
int tree[500005];
struct node
{
int cnt,va;
bool operator < (const node &a) const
{
return va<a.va;
}
}a[500005];
void add(int x,int va)
{
while(x<=n)
{
tree[x]+=va;
x+=lowbit(x);
}
}
int sum(int x)
{
int now=0;
while(x>0)
{
now+=tree[x];
x-=lowbit(x);
}
return now;
}
int main()
{
while(1)
{
scanf("%d",&n);
if(n==0)
break;
long long ans=0;
memset(tree,0,sizeof tree);
for(int i=1;i<=n;i++)
{
a[i].cnt=i;
scanf("%d",&a[i].va);
}
sort(a+1,a+n+1);
for(int i=n;i>=1;i--)
{
ans+=sum(a[i].cnt);
add(a[i].cnt,1);
//printf("*%d\n",ans);
}
printf("%lld\n",ans);
}
}
Stars (POJ2352)
题目大意:给定一些星星,这些星星是有等级的。而它们的等级就是它的左下角星星的个数(x不小于x0,y不小于y0)。并且在给定y是不下降的,在y相同的情况下x是不下降的。
因为这道题的特殊性,输入数据都是不下降的,所以我们不用两维的树状数组,就用一个一维的就可以了。我们只需要按照输入的顺序处理每个星星的x,统计一下当前星星的等级就可以了。
#include<cstdio>
#include<cstring>
#include<algorithm>
#define lowbit(x) (x&(-x))
using namespace std;
int n;
int tree[32005],a[15005];
void add(int x,int va)
{
while(x<=32001)
{
tree[x]+=va;
x+=lowbit(x);
}
}
int sum(int x)
{
int now=0;
while(x>0)
{
now+=tree[x];
x-=lowbit(x);
}
return now;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
int x,y;
scanf("%d%d",&x,&y);
x++;
add(x,1);
a[sum(x)-1]++;
}
for(int i=0;i<n;i++)
printf("%d\n",a[i]);
}
Color the ball
差分的第一道题,很水,只需在每次涂颜色的x位置+1,y+1位置-1,在进行统计就可以。
代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n;
int a[100005];
int main()
{
while(1)
{
scanf("%d",&n);
if(n==0)
break;
memset(a,0,sizeof a);
for(int i=1;i<=n;i++)
{
int x,y;
scanf("%d%d",&x,&y);
a[x]++;
a[y+1]--;
}
int now=0;
for(int i=1;i<n;i++)
{
now+=a[i];
printf("%d ",now);
}
now+=a[n];
printf("%d\n",now);
}
}
Apple Tree
题目大意:给定些点这些点有一些无向边连接,1为根。C x表示第x个节点有苹果的状态进行改变,Q x即询问x节点及其子树上的苹果的个数。
首先这道题我们做出一个dfs序以及这个节点的子树长度,然后这道题就变成了区间求和就可以了,然后运用树状数组就可以很好的解决。
但是要注意一个地方,我之前vector用的是向量数组在vjudge上是T的,而改成了二维向量就A了。好像是因为数组比较大,初始化的时间比较长。改成二维的向量后毫无压力的过了。
代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#define MAXN 100005
#define lowbit(x) (x&(-x))
using namespace std;
int n,m,cnt_d=1;
int t[MAXN],len[MAXN],tree[MAXN];
bool vis[MAXN];
vector <int> a[MAXN];
void dfs(int x)
{
int now=0;
t[x]=cnt_d++;
for(int i=0;i<a[x].size();i++)
{
if(vis[a[x][i]])
continue;
vis[a[x][i]]=1;
dfs(a[x][i]);
now++;
len[x]+=len[a[x][i]];
}
len[x]+=now;
}
void add(int x,int va)
{
while(x<=n)
{
tree[x]+=va;
x+=lowbit(x);
}
}
int sum(int x)
{
int now=0;
while(x>0)
{
now+=tree[x];
x-=lowbit(x);
}
return now;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<n;i++)
{
int x,y;
scanf("%d%d",&x,&y);
a[x].push_back(y);
a[y].push_back(x);
add(i,1);
}
add(n,1);
vis[1]=1;
dfs(1);
scanf("%d",&m);
for(int i=1;i<=m;i++)
{
int num;
char s[5];
scanf("%s%d",s,&num);
if(s[0]=='C')
{
add(t[num],vis[t[num]]?-1:1);
vis[t[num]]=!vis[t[num]];
}
else
printf("%d\n",sum(t[num]+len[num])-sum(t[num]-1));
}
}
Matrix
题目大意:给定n*n的矩阵,两个操作,一个询问x1,y1的矩阵的状态,另一个操作是将x1,y1,x2,y2的矩阵进行取反操作。
首先对于取反操作,因为要用树状数组来做我们可以每次取反四个矩阵来达到同样的效果。而这个四个矩阵分别是从(1,1)到(x1-1,y1-1)(x2,y1-1)(x1-1,y2)(x2,y2)。这样一来就可以用二维树状数组的方式将这个矩阵存下,那么在询问操作时也就是非常简单。
代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#define MAXN 100005
#define lowbit(x) (x&(-x))
using namespace std;
int T,n,m;
int tree[1007][1007];
void add(int x,int y)
{
for(int i=x;i>0;i-=lowbit(i))
for(int j=y;j>0;j-=lowbit(j))
tree[i][j]^=1;
}
int sum(int x,int y)
{
int now=0;
for(int i=x;i<=n;i+=lowbit(i))
for(int j=y;j<=n;j+=lowbit(j))
{
now+=tree[i][j];
now%=2;
}
return now;
}
int main()
{
scanf("%d",&T);
while(T--)
{
memset(tree,0,sizeof tree);
scanf("%d%d",&n,&m);
for(int j=0;j<=n;j++)
memset(tree+j,0,4*(n+2));
for(int i=1;i<=m;i++)
{
char s[5];
scanf("%s",s);
if(s[0]=='C')
{
int x1,x2,y1,y2;
scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
add(x2,y2);
add(x2,y1-1);
add(x1-1,y2);
add(x1-1,y1-1);
}
else if(s[0]=='Q')
{
int x,y;
scanf("%d%d",&x,&y);
printf("%d\n",sum(x,y));
}
}
printf("\n");
}
}