您的位置:首页 > 其它

树状数组小结

2013-11-16 16:00 204 查看
树状数组功能:(适用于:要求不断求区间和 || 不断更新区间)

1.快速 求区间和

2.快速 更新区间

具体详细资料 见大白书 和 辅导资料.

树状数组 ---运用了分块的思想。

用lowbit函数来得到 地址下标.

基本操作:

1.lowbit()函数

原理:利用了负数在计算机中的存储形式(按位取反+1),你会发现负数与其绝对值在存储上 

最后一个1的位置是相同的 那么用& 就可以 把一个数字 分为几块。树状数组的操作就全基于此!

2.sum()求区间和函数

3.add()更新区间函数

例题:

poj2352

思路 ,因为星星事先已经按y从小到大排序了,那么只需要从第一个星星开始 得到它的级数;求级数就要用到区间求和,得到级数后,又要把

该星星加入树状数组里,即更新(是为了后面星星算级数服务)。

//Accepted	384K	141MS
#include <iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define MAX 32010
int n;
int tree[MAX];
int level[MAX];
int lowbit(int x)
{
return x&(-x);
}
void Add(int x)
{
while(x<MAX)
{
tree[x]+=1;
x+=lowbit(x);
}
}
int Sum(int x)
{
int s=0;
while(x>0)
{
s+=tree[x];
x-=lowbit(x);
}
return s;
}
int main()
{
scanf("%d",&n);
memset(tree,0,sizeof(tree));
memset(level,0,sizeof(level));
for(int i=1;i<=n;i++)
{
int x,y;
scanf("%d%d",&x,&y);
x++;
level[Sum(x)]++;
Add(x);
}
for(int i=0;i<n;i++)
printf("%d\n",level[i]);
return 0;
}


hrbust 1400

和上题大同小异,只是需要注意这里没事先排好序,且排序是要把 为同一起点的车 要按速度从大到小排序!

//Accepted		262ms
#include <iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define MAX 100002
#define N 1000002
int n;
int c
;
struct Node
{
int x,v;
bool operator<(const Node& a) const
{
if(a.x==x) return v>a.v;
else
return x<a.x;
}
}car[MAX];
int lowbit(int x)
{
return x&(-x);
}
void Add(int x)
{
while(x<N)
{
c[x]+=1;
x+=lowbit(x);
}
}
int sum(int x)
{
long long s=0;
while(x>0)
{
s+=c[x];
x-=lowbit(x);
}
return s;
}
int solve()
{
memset(c,0,sizeof(c));
int ans=0;
for(int i=1;i<=n;i++)
{
ans+=i-sum(car[i].v)-1;
Add(car[i].v);
}
return ans;
}
int main()
{
while(~scanf("%d",&n))
{
for(int i=1;i<=n;i++)
{
scanf("%d%d",&car[i].x,&car[i].v);
}
sort(car+1,car+1+n);
// cout<<"\n*****************************\n";
// for(int i=1;i<=n;i++)
//  printf("%d ",car[i].x);
//cout<<endl;
//for(int i=1;i<=n;i++)
//printf("%d ",car[i].v);
//   cout<<"\n*****************************\n";
printf("%lld\n",solve());
}
return 0;
}


hrbust 1161

就需要注意的是:树状数组的更新操作  有一个所谓的时间限制!

加一个记录时间的 数组就OK!

//	560ms
#include <iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define MAX 100002
int n,q,t;
int tim[MAX];
int c[MAX];
int lowbit(int x)
{
return x&(-x);
}
int sum(int x)
{
int s=0;
while(x>0)
{
s+=c[x];
x-=lowbit(x);
}
return s;
}
void add(int x)
{
while(x<MAX)
{
c[x]+=1;
x+=lowbit(x);
}
}
int main()
{
int T;
scanf("%d",&T);
int cas=1;
while(T--)
{
printf("Case %d:\n",cas++);/**注意这个输出位置*/
memset(c,0,sizeof(c));
int Attack=0;
scanf("%d%d%d",&n,&q,&t);
for(int i=1;i<=n;i++)
tim[i]=-t;/**为后面铺垫**/

for(int i=1;i<=q;i++)
{
char s[6];
scanf("%s",s);
if(s[0]=='A')
{
Attack++;
int a;
scanf("%d",&a);
if(tim[a]+t<=Attack)/**判断能不能抵消攻击**/
{
tim[a]=Attack;/**能抵消则从这时刻重新计算冷却时间**/
}
else
add(a);
}
else
{
int fir,last;
scanf("%d%d",&fir,&last);
if(fir>last) swap(fir,last);
printf("%d\n",sum(last)-sum(fir-1));
}
}
}
return 0;
}


poj2085

这一题 其实主要是构造题!不过其中要用到 树状数组!

大意:给一个n,和一个m。n为1~n的序列.m为多少个 逆序对.

要你求出 由1~n数字组成的 拥有m个逆序对的 字典序最小的排列!

第一步:要求出m<=1+2+3+......+k; 中的k(表示这个逆序对最多由几个数字组成)--明显用树状数组求 区间和!+二分查找k

第二步:应该按升序输出1~n-k个数放答案序列的最前面!因为只需要后面的k序列就可以满足m个逆序对的条件!

第三步:要求出m=1+2+3+.....+x中的x  这个x一定是介于 k和k-1之间的。不然上一步求出的k就不是最大的。

那么 计算 k-x;就可以知道实际这个要得到的序列  与  k个数完全降序排列 构成的逆序
 少几个逆序对!

那么把 n-(k-x) 这个数放到 这个完全降序的逆序列的最前面 就可以 减少k-x个逆序对。就得到了答案。

第四步: 注意输出和格式就行了!

//xdq	2085	Accepted	524K	110MS	C++	1438B
#include <iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define MAX 50002
#define ll __int64
ll c[MAX];
ll lowbit(ll x)
{
return x&(-x);
}
ll sum(ll x)
{
ll s=0;
while(x>0)
{
s+=c[x];
x-=lowbit(x);
}
return s;
}
void add(ll x)
{
ll val=x;
while(x<MAX)
{
c[x]+=val;
x+=lowbit(x);
}
}
void Init()
{
for(ll i=1;i<=MAX;i++)
add(i);
}
ll find_k(ll key,ll l)
{
ll k,mid;
ll r=1;
while(l>=r)
{
mid=(l-r)/2+r;
if(sum(mid)>=key)
{
k=mid;
l=mid-1;
}
else {r=mid+1;}
}
return k;
}
int main()
{
//freopen("ss.txt","r",stdin);
//freopen("tt.txt","w",stdout);
Init();
ll n;
ll m;
while(~scanf("%I64d%I64d",&n,&m))
{
if(n==-1&&m==-1) break;
ll k,i;
if(m==0)
{
for(i=1;i<n;i++)
printf("%I64d ",i);
printf("%I64d\n",i);
continue;
}
k=find_k(m,n);
for( i=1;i<n-k;i++) /**前n-k个按升序排*/
printf("%I64d ",i);

int x=k-(sum(k)-m);
printf("%I64d ",n-k+x);/**把这个数放在n前面就可以消除多出的 k-x个逆序对**/

for( i=n;i>n-k+x;i--) /**然后就把剩余的降序排列就ok 了!**/
printf("%I64d ",i);
for( i=n-k+x-1;i>n-k;i--)
printf("%I64d ",i);
printf("%I64d\n",n-k);
}
return 0;
}

如何用树状数组求逆序数呢?(当然可以用归并排序求!)

第一种方法:只要从输入序列的尾 开始读数字,读一个就求它(不包含它)之前的 区间和!

那么这就是它后面有几个小于它的数!即逆序对!

第二种方法:当然你从输入序列头往后读 也可以,不过就是反向思维,就是求它之前有几个比它小的,然后拿当前总数减去 就得到

前面有几个比它大的,即逆序对!

这个题目因为 每个数字可能 大到10^9 那么树状数组 下标是开不下的!

所以这里用到了  离散化  ,把输入序列的 每个数组之间的
相对大小求出来了!

所有这下 树状数组下标就只跟 数据规模 n 有关了!

SGU180

#include <iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
#define N 70000
struct Node
{
int data;
int id;
bool operator<(const Node t) const
{
return data<t.data;
}
}a
;
int b
;
int n;
long long c
;
int lowbit(int x)
{
return x&(-x);
}
long long sum(int x)
{
long long s=0;
for(int i=x;i>0;i-=lowbit(i))
s+=c[i];
return s;
}
void add(int x)
{
for(int i=x;i<=n;i+=lowbit(i))
c[i]+=1;
}
int main()
{

while(~scanf("%d",&n))
{
int tot=0,p=-1;
memset(c,0,sizeof(c));
long long ans=0;

for(int i=1;i<=n;i++)
{
scanf("%d",&a[i].data);
a[i].id=i;
}
sort(a+1,a+1+n);
for(int i=1;i<=n;i++)
{
if(p!=a[i].data)/**相同的一定要相对大小相同
不然过不了第二组数据*/
{
tot++;
p=a[i].data;
}
b[a[i].id]=tot;
}

for(int i=n;i>=1;i--)
{
ans+=sum(b[i]-1);/**注意这里是要求不包含本身的和!**/
add(b[i]);

}
cout<<ans<<endl;
}

return 0;
}


poj1195

二维的树状数组。即把分块的思想 扩展运用!

//xdq	1195	Accepted	4880K	532MS	C++	1036B
/**纯裸题。理解二维的lowbit分块!**/
#include<stdio.h>
#include<string.h>
#define N 1100
int c

,n,arr

;
int lowbit(int x)
{
return x&(-x);
}
void update(int x,int y,int num)
{
int i,j;
for(i=x;i<=n;i+=lowbit(i))
for(j=y;j<=n;j+=lowbit(j))
c[i][j]+=num;
}
int sum(int x,int y)
{
int i,j,s=0;
for(i=x;i>0;i-=lowbit(i))
for(j=y;j>0;j-=lowbit(j))
s+=c[i][j];
return s;
}
int getsum(int x1,int y1,int x2,int y2)
{
return sum(x2,y2)-sum(x1-1,y2)-sum(x2,y1-1)+sum(x1-1,y1-1);
}
int main()
{
int op,x,y,l,b,r,t,a;
while(scanf("%d",&op)!=EOF)
{
if(op==0)
{
scanf("%d",&n);
memset(c,0,sizeof(c));
}
else if(op==1)
{
scanf("%d%d%d",&x,&y,&a);
update(x+1,y+1,a);
}
else if(op==2)
{
scanf("%d%d%d%d",&l,&b,&r,&t);
int ans=getsum(l+1,b+1,r+1,t+1);
printf("%d\n",ans);
}
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  树状数组