树状数组的应用

树状数组是一种较为高级的数据结构,下面总结一下具体应用(按照树神讲的顺序:

树状数组Binary Indexed Trees简称BIT。其平摊了修改和查询的时间复杂度,使他们都成为O(logN)的级别!

1.单点修改区间查询。(大家都会不再赘述。

2.区间修改单点查询,然后利用树状数组进行差分的求和,然后即可实现区间修改。

伪代码:add(x,1);add(y+1,-1);x,y为区间修改。

3.单点修改加求区间第k小的数字,很不好写呢,细节比较重要,wa了3次。poj 2985

#include<bits/stdc++.h>
#include<iostream>
#include<cmath>
#include<ctime>
#include<cstdio>
#include<iomanip>
#include<cstring>
#include<string>
#include<stack>
#include<algorithm>
#include<map>
#include<queue>
#include<deque>
#include<vector>
#include<set>
using namespace std;
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
void put(int x)
{
    if(x==0){putchar('0');putchar('\n');return;}
    if(x<0)putchar('-'),x=-x;
    int num=0;char ch[50];
    while(x)ch[++num]=x%10+'0',x/=10;
    while(num)putchar(ch[num--]);
    putchar('\n');return;
}
const int maxn=200002;
int a[maxn],c[maxn],f[maxn];
int n,m;
int getfather(int x){return x==f[x]?x:f[x]=getfather(f[x]);}
void add(int x,int y){for(;x;x-=x&(-x))c[x]+=y;return;}
int ask(int x){int ans=0;for(;x<=n;x+=x&(-x))ans+=c[x];return ans;}
int main()
{
    //freopen("1.in","r",stdin);
    n=read();m=read();
    for(int i=1;i<=n;i++)f[i]=i,a[i]=1,add(a[i],1);
    for(int i=1;i<=m;i++)
    {
        int p,x,y;
        p=read();
        if(p==0)
        {
            x=read();y=read();
            int xx=getfather(x);
            int yy=getfather(y);
            if(xx!=yy)
            {
                f[xx]=yy;
                add(a[xx],-1);//wa的原因
                add(a[yy],-1);//让他们的父亲减
                a[yy]+=a[xx];a[xx]=0;
                add(a[yy],1);
            }
            else continue;
        }
        if(p==1)
        {
            x=read();
            int l=1,r=n;
            while(l+1<r)
            {
                int mid=(l+r)>>1;
                if(ask(mid)>=x)l=mid;//寻找那个刚好合适的人呢
                else r=mid;//一直寻找
            }
            if(ask(r)>=x)put(r);//r比l更优注意~!
            else put(l);
        }
    }
    return 0;
}
View Code

当然还有一种二进制的方法直接得出不过需要转换一下,本人还不懂..

#include<bits/stdc++.h>
#include<iostream>
#include<cmath>
#include<ctime>
#include<cstdio>
#include<iomanip>
#include<cstring>
#include<string>
#include<stack>
#include<algorithm>
#include<map>
#include<queue>
#include<deque>
#include<vector>
#include<set>
using namespace std;
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
void put(int x)
{
    if(x==0){putchar('0');putchar('\n');return;}
    if(x<0)putchar('-'),x=-x;
    int num=0;char ch[50];
    while(x)ch[++num]=x%10+'0',x/=10;
    while(num)putchar(ch[num--]);
    putchar('\n');return;
}
const int maxn=200002;
int a[maxn],c[maxn],f[maxn];
int n,m,sum;
int getfather(int x){return x==f[x]?x:f[x]=getfather(f[x]);}
void add(int x,int y){for(;x<=n;x+=x&(-x))c[x]+=y;return;}
int ask(int x){int ans=0;for(;x<=n;x+=x&(-x))ans+=c[x];return ans;}
int main()
{
    //freopen("1.in","r",stdin);
    n=read();m=read();sum=n;
    for(int i=1;i<=n;i++)f[i]=i,a[i]=1,add(a[i],1);
    for(int i=1;i<=m;i++)
    {
        int p,x,y;
        p=read();
        if(p==0)
        {
            x=read();y=read();
            int xx=getfather(x);
            int yy=getfather(y);
            if(xx!=yy)
            {
                f[xx]=yy;
                add(a[xx],-1);//wa的原因
                add(a[yy],-1);//让他们的父亲减
                a[yy]+=a[xx];a[xx]=0;
                add(a[yy],1);
                sum--;//还有几组
            }
            else continue;
        }
        if(p==1)
        {
            x=read();
            int ans=0,cnt=0;int u=sum-x+1;//卡好范围第u小
            for(int i=20;i>=0;i--)
            {
                ans+=(1<<i);
                if(ans>n||cnt+c[ans]>=u)ans-=(1<<i);
                else cnt+=c[ans];
            }
            put(ans+1);
        }
    }
    
    return 0;
}
View Code

4.可以进行二维的树状数组,也就是一维变二维;

二维的树状数组罢了其实很简单。vijos 1512;

#include<iomanip>
#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<string>
#include<map>
#include<algorithm>
#include<ctime>
#include<vector>
#include<queue>
#include<deque>
#include<set>
#include<stack>
using namespace std;
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
void put(int x)
{
    if(x==0)
    {
        putchar('0');
        putchar('\n');
        return;
    }
    if(x<0)
    {
        putchar('-');
        x=-x;
    }
    int num=0;char ch[25];
    while(x)ch[++num]=x%10+'0',x/=10;
    while(num)putchar(ch[num--]);
    putchar('\n');
}
const int maxn=1200;
int n,p;
int c[maxn][maxn];
void add(int x,int y,int z)
{
    for(int i=x;i<=n;i+=i&(-i))
        for(int j=y;j<=n;j+=j&(-j))
            c[i][j]+=z;
}
int ask(int x,int y)
{
    int ans=0;
    for(int i=x;i;i-=i&(-i))
        for(int j=y;j;j-=j&(-j))
            ans+=c[i][j];
    return ans;
}
int main()
{
    //freopen("1.in","r",stdin);
    n=read()+1;
    while(1)
    {
        p=read();
        if(p==3)break;
        if(p==1)
        {
            int x,y,k;
            x=read()+1;y=read()+1;k=read();
            add(x,y,k);
        }
        if(p==2)
        {
            int x,x1,s,s1;
            x=read()+1;x1=read()+1;s=read()+1;s1=read()+1;
            put(ask(s,s1)-ask(s,x1-1)-ask(x-1,s1)+ask(x-1,x1-1));
        }
    }
    return 0;
}
View Code

5.图腾计数树状数组应用 CH4201

数出左边比当前点高的和低和比右边点高或低的相乘累加即可。一遍a~!

#include<bits/stdc++.h>
#include<iomanip>
#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<string>
#include<map>
#include<algorithm>
#include<ctime>
#include<vector>
#include<queue>
#include<deque>
#include<set>
#include<stack>
using namespace std;
inline long long read()
{
    long long x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
void put(long long x)
{
    if(x==0)
    {
        putchar('0');
        putchar(' ');
        return;
    }
    if(x<0)
    {
        putchar('-');
        x=-x;
    }
    long long num=0;char ch[60];
    while(x)ch[++num]=x%10+'0',x/=10;
    while(num)putchar(ch[num--]);
    putchar(' ');
}
const long long maxn=200002;
long long a[maxn],c[maxn],u[maxn],r[maxn],l[maxn];
long long w[maxn],y[maxn];
long long n,ans=0,num=0;
void add(long long x,long long y){for(;x<=n;x+=x&(-x))c[x]+=y;return;}
long long ask(long long x){long long ans=0;for(;x;x-=x&(-x))ans+=c[x];return ans;}
void add1(long long x,long long y){for(;x;x-=x&(-x))u[x]+=y;return;}
long long ask1(long long x){long long ans=0;for(;x<=n;x+=x&(-x))ans+=u[x];return ans;}
int main()
{
    //freopen("1.in","r",stdin);
    n=read();
    for(long long i=1;i<=n;i++)
    {
        a[i]=read();
        add(a[i],1);
        add1(a[i],1);
        l[i]=ask1(a[i]+1);
        w[i]=ask(a[i]-1);
    }
    memset(u,0,sizeof(u));
    memset(c,0,sizeof(c));
    for(long long i=n;i>=1;i--)
    {
        add(a[i],1);
        add1(a[i],1);
        r[i]=ask1(a[i]+1);
        y[i]=ask(a[i]-1);
    }
    for(long long i=1;i<=n;i++)
    {
        ans+=l[i]*r[i];
        num+=w[i]*y[i];
    }
    put(ans);put(num);
    return 0;
}
View Code

关于逆序对的求解在另一篇博文之中,本文不再赘述。

6.二维数点

直接转换一下可以考虑排序如果区间无序的话,然后进行数点即可。

#include<bits/stdc++.h>
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<iomanip>
#include<ctime>
#include<queue>
#include<map>
#include<vector>
#include<stack>
#include<algorithm>
using namespace std;

inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}

const int maxn=60002;
int n,c[maxn],maxx;
int x[maxn],y[maxn];

void add(int x,int y){for(;x<=maxx;x+=x&(-x))c[x]+=y;}

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

int main()
{
    //freopen("1.in","r",stdin);
    n=read();
    for(int i=1;i<=n;i++)
    {
        x[i]=read();y[i]=read();
        x[i]+=1;y[i]+=1;
        maxx=max(maxx,x[i]);
        
    }
    for(int i=1;i<=n;i++)
    {
        add(x[i],1);
        printf("%d\n",ask(x[i])-1);
    }
    return 0;
}
View Code

8.一道比较有思维难度的题

很朴实的题,除了范围有点大似乎没什么不好这个也其实很简单,具体细节看代码。

#include<bits/stdc++.h>
#include<iomanip>
#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<string>
#include<map>
#include<algorithm>
#include<ctime>
#include<vector>
#include<queue>
#include<deque>
#include<set>
#include<stack>
using namespace std;
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
const int maxn=1000008;
struct wy
{
    int x,y;
    int an,id;
}t[maxn];
int n,m;
int a[maxn],pre[maxn];//prefix前缀。
int cmp(wy x,wy y){if(x.x==y.x)return x.y<y.y;return x.y<y.y;}
int my(wy x,wy y){return x.id<y.id;}
int c[maxn],ans=0;
void add(int x,int y){for(;x<=n;x+=x&(-x))c[x]+=y;return;}
int ask(int x){int ans=0;for(;x;x-=x&(-x))ans+=c[x];return ans;}
void put(int x)
{
    if(x==0)
    {
        putchar('0');
        putchar('\n');
        return;
    }
    if(x<0)
    {
        putchar('-');
        x=-x;
    }
    int num=0;char ch[16];
    while(x)ch[++num]=x%10+'0',x/=10;
    while(num)putchar(ch[num--]);
    putchar('\n');
}
int main()
{
    //freopen("1.in","r",stdin);
    n=read();
    for(int i=1;i<=n;i++)a[i]=read()+1;
    m=read();
    for(int i=1;i<=m;i++)t[i].x=read(),t[i].y=read(),t[i].id=i;
    sort(t+1,t+1+m,cmp);
    //for(int i=1;i<=m;i++)printf("%d %d\n",t[i].x,t[i].y);
    for(int i=1,j=1;i<=n;i++)
    {
        if(pre[a[i]]==0)
        {
            add(i,1);
            pre[a[i]]=i;
        }
        else 
        {
            add(pre[a[i]],-1);
            add(i,1);
            pre[a[i]]=i;
        }
        while(i==t[j].y)
        {
            t[j].an=ask(t[j].y)-ask(t[j].x-1);
            j++;
        }
    }
    sort(t+1,t+1+m,my);
    for(int i=1;i<=m;i++)put(t[i].an);
    return 0;
}
View Code

最后:

注意:

因为lowbit(0)=0所以树状数组的下标只能从1开始,如果题干中出现0的情况,把所有输入数据都+1是个不错的解决方法。

 

树状数组可以做到区间修改,区间查询,方法为两个树状数组维护原序列。

树状数组的求和操作适用于所有满足结合律的操作,例如异或。

谁道闲情抛掷久,每到春来,惆怅还依旧。

 

posted @ 2018-12-08 12:33  chdy  阅读(465)  评论(0编辑  收藏  举报