树状数组小结

树状数组功能:(适用于:要求不断求区间和 || 不断更新区间

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[N];
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[N];
int b[N];
int n;
long long c[N];
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][N],n,arr[N][N];
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;
}




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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值