树状数组

来源:互联网 发布:淘宝靠谱美国代购 编辑:程序博客网 时间:2024/06/09 20:19

(我又来写题解了~~~)

树状数组(二叉搜索树)

定义c[i]维护的是[iilowbiti+1]这段区间的值,这个lowbit等会解释。树状数组,他的时间复杂度为OlogN,而且常数小,还好写,简直就是oi中的大法。并且我们可以“感受”到,如果只有一般的修改与一般的询问最大值,前缀和等,都完全可以替代线段树。(当然,有些时候他也替代不了线段树,比如主席树时)。下面来直观看看这个数组c的管辖范围。
TREE ARRAY
由此可见,这个数组像一棵树一样,故曰树状数组(实际上本来就是一棵树)。
下面解释lowbitx的含义lowbitx是最大的2k,使得2kx的约数,而2k+1不是其约数。即x的约数中最大的二的幂。也可以说是x在二进制时最后一个1所代表的值。

1.基本操作:

单点更新:

int update(int x,int val){    while (x<=n)    {    c[x]+=val;    x+=lowbit(x);    }}

区间查询:

int query(int x){    int ans = 0;    while (x)    {    ans += c[x];    x-=lowbit(x);    }}

实际上也可以区间更新,区间查询。记录一个新数组a[i]=x[i]x[i1](原数组为x),那么修改时修改差分,然后差分的前缀和就是单点的数。而区间查询则需要推公式,推出来为x×a[i]a[i]×(i1);那么就只需要再记录一个新的数组记录a[i]×(i1)

例题

  • Codevs 1082
    题目:区间修改,区间查询。
    代码:
#include<cstdio>#include<algorithm>const int size = 200005;using namespace std;typedef long long ll; ll del[size],del2[size],num[size];ll n , m;ll dodo ,l ,r,val;ll lowbit(ll x){return  (x &(-x));}void update(ll x,ll val,ll *arr){    while (x<=n)    {        arr[x] += val;        x += lowbit(x);    }}ll query(ll x,ll *arr){    ll ans = 0;    while(x)    {        ans += arr[x];        x -= lowbit(x);    }    return ans;}int main(){    scanf("%lld" , &n);    for (ll i = 1;i<=n;i++)    {scanf("%lld" , &num[i]);     update(i,num[i] - num[i-1],del);     update(i,(i-1) * (num[i] - num[i-1]) ,del2);    }    scanf("%lld",&m);    for (ll i =1;i <=m ;i++)    {        scanf("%lld",&dodo);        if (dodo == 1)        {            scanf("%lld%lld%lld",&l,&r,&val);            update(l,val,del);            update(r+1,-val,del);            update(l,val * (l-1),del2);            update(r+1,-val * r,del2);        }        else        {            scanf("%lld%lld",&l,&r);            ll lle =  (l-1) * query(l-1,del) - query(l-1,del2);            ll rr =  r * query(r,del) - query(r,del2);            printf("%lld\n",rr-lle);        }    }        return 0;}
  • Bzoj 1452: [JSOI2009]Count

题目: 略

题解:水题啊,注意到c的值很小,mn的也很小,那么我们就记录树状数组c[d][m][n]表示d这个数在0,0mn这个矩形内出现次数,然后就完了。
代码:

#include<cstdio>#include<algorithm>#include<cstring>int n,m,q;//n 对应 x //m 对应 y  int c[101][310][310];int mat[310][310];int lowbit(int x){return (x&(-x));}using namespace std;void update(int x,int y,int pre,int now){        int x0 = x;int y0 = y;        while (x0<=n)        {             y0 = y;            while (y0 <= m)            {            c[now][x0][y0]++;            y0 += lowbit(y0);            }            x0 += lowbit (x0);        }         x0 = x; y0 = y;        while (x0<=n)        {            y0 = y;            while (y0 <= m)            {            c[pre][x0][y0]--;            y0 += lowbit(y0);            }            x0 += lowbit (x0);        }}int query(int val,int x,int y){    int ans = 0;    int x0 = x;int y0 = y;    while (x0)    {        y0 = y;        while (y0)        {            ans += c[val][x0][y0];            y0 -= lowbit(y0);         }        x0 -= lowbit (x0);    }       return ans;}int main(){    scanf("%d%d",&n,&m);    memset(c,0,sizeof c);    for (int x = 1;x <= n ;x++)       for (int y = 1;y <= m;y++)       {       scanf("%d",&mat[x][y]);       update(x,y,0,mat[x][y]);       }    scanf("%d",&q);    while (q--)    {        int dodo;        scanf("%d",&dodo);        if (dodo == 1)        {            int x,y,val;            scanf("%d%d%d",&x,&y,&val);            update(x,y,mat[x][y],val);            mat[x][y] = val;        }else        if (dodo == 2)        {            int x1,x2,y1,y2,val;            scanf("%d%d%d%d%d",&x1,&x2,&y1,&y2,&val);            int ans = query(val,x2,y2)+ query(val,x1-1,y1-1) - query(val,x2,y1-1)- query(val,x1-1, y2);            printf("%d\n",ans);        }    }    return 0;}
  • bzoj 2743[HEOI2012]采花
    题目:
    萧芸斓是Z国的公主,平时的一大爱好是采花。
    今天天气晴朗,阳光明媚,公主清晨便去了皇宫中新建的花园采花。花园足够大,容纳了n朵花,花有c种颜色(用整数1-c表示),且花是排成一排的,以便于公主采花。公主每次采花后会统计采到的花的颜色数,颜色数越多她会越高兴!同时,她有一癖好,她不允许最后自己采到的花中,某一颜色的花只有一朵。为此,公主每采一朵花,要么此前已采到此颜色的花,要么有相当正确的直觉告诉她,她必能再次采到此颜色的花。由于时间关系,公主只能走过花园连续的一段进行采花,便让女仆福涵洁安排行程。福涵洁综合各种因素拟定了m个行程,然后一一向你询问公主能采到多少朵花(她知道你是编程高手,定能快速给出答案!),最后会选择令公主最高兴的行程(为了拿到更多奖金!)。
    题解:
    实际上就是问你区间上有几个落单的数。那么我们可以打一下标记,先把询问按左端点排序,然后记录每个数的下一个与其值相等的数的位置next,然后先把每个值第二个加入树状数组,接着做时,就遇到一个i,去掉next[i],加上next[next[i]]。(因为这样就可以使如果只有一个,一正一负刚好可以抵了,反之,也对,把第二个也加上是考虑了第零个)。
    代码:
#include<cstdio>#include<algorithm>#include<cstring>using namespace std;const int maxn = 1000110;int lowbit(int x){ return (x & (-x));}int n , m  ,ww;int c[maxn] , a[maxn] , ans[maxn] , next[maxn] , pre[maxn ] ;bool vis[maxn];struct q{    int l ,r ,id;     bool operator < (const q &cx) const{        if (l == cx.l) return r < cx.r;        return l < cx.l;    }}qq[maxn];void update (int x,int del){    while (x <= n)    {        c[x] += del;        x += lowbit(x);    }}int query(int x){    if (x==0) return 0;    int ans = 0;    while (x)    {        ans += c[x];        x -= lowbit (x);    }    return ans;}int main(){    scanf("%d%d%d",&n,&ww,&m);    for (int i = 1 ;i <= n ;i++)    scanf("%d" , &a[i]);    for (int i = 1;i <= m;i++)    scanf("%d%d", &qq[i].l, &qq[i].r), qq[i].id = i;            sort(qq + 1 , qq + 1 + m);    memset(vis,0,sizeof vis);     for (int i = 1;i <= n;i++)    {        if (pre[ a[i] ])    next[ pre[ a[i] ] ] = i;        else vis[i] = 1;        pre[ a[i] ] = i;    }    for (int i = 1;i <= n;i++)    if (vis[i] && next[i])    update(next[i],1);     int nowr = 1;    for(int i = 1;i <= m;i++)    {        while (nowr < qq[i].l)        {        if (next[nowr])             {            update(next[nowr] , -1);            if (next [ next [ nowr ]  ]) update(next[ next[nowr] ] , 1); }              nowr++;         }    ans[qq[i].id] = query(qq[i].r) ;    }   for (int i = 1;i <= m;i++)    printf("%d\n",ans[i]);    return 0;}
  • bzoj 1878: [SDOI2009]HH的项链
    题解:
    与上题相似,但这题按右端点排序。我们记录一个a[i]表示i是不是当前区间内x[i]这个值最靠右的(x是原序列),然后就统计一下就完了。
    代码:
#include<cstdio>#include<algorithm>#include<cstring>using namespace std;const int maxn = 200110;int lowbit(int x){ return (x & (-x));}int n , m;int c[maxn] , a[maxn] , ans[maxn] , next[maxn] , pre[maxn * 5] ;struct q{    int l ,r ,id;     bool operator < (const q &cx) const{        if (r == cx.r) return l < cx.l;        return r < cx.r;    }}qq[maxn];void update (int x,int del){    while (x <= n)    {        c[x] += del;        x += lowbit(x);    }}int query(int x){    if (x==0) return 0;    int ans = 0;    while (x)    {        ans += c[x];        x -= lowbit (x);    }    return ans;}int main(){    scanf("%d",&n);    for (int i = 1 ;i <= n ;i++)    scanf("%d" , &a[i]);    scanf("%d" , &m);    for (int i = 1;i <= m;i++)    scanf("%d%d", &qq[i].l, &qq[i].r), qq[i].id = i;            sort(qq + 1 , qq + 1 + m);    for (int i = 1;i <= n;i++)    {        if (pre[ a[i] ])    next[ pre[ a[i] ] ] = i;        pre[ a[i] ] = i;    }    memset(pre , 0 , sizeof pre);    for (int i = 1;i <= n;i++)   if (next[i]) pre[ next[i] ] = i;    int nowr = 0;    for(int i = 1;i <= m;i++)    {        while (nowr < qq[i].r)        {        nowr++;        if (pre[nowr]) update(pre[nowr] , -1);        if (nowr) update(nowr , 1);         }           ans[qq[i].id] = query(qq[i].r) - query(qq[i].l-1);    }       for (int i = 1;i <= m;i++)    printf("%d\n",ans[i]);    return 0;}
  • bzoj 4240: 有趣的家庭菜园

题目:
题解:
(这题好啊)*最小交换次数就是最后序列的关于原位置的逆序对*,然后我们可以证明最后数列是一个先增后减的样子。(好不容易遇到一个我会证的,当然要证一证)。如下:首先最大的可以随便放,然后我们考虑他左边的第一个数Ai,那么Ai右边有比他大的了,所以Ai左边都不能比Ai大,所以Ai[1,i]最大的,同理可证Ai1也是[1,Ai1]最大的,于是在最大的以左为单增,同理可证右边为单减。好了,接着把草从大到小排一次序,那么我们新放的数一定在已经放的数的最左边或最右边,然后开始贪心,每次加入其位置,然后看是放左边还是右边。因为大的已经放了,较小的无论怎么放都不会影响之前的大的,所以可以贪心。
总结:这道题最大的转点在于:最小交换次数就是最后序列的关于原位置的逆序对这一结论,一定要记熟。
代码:(压了行的,别打我)

#include<cstdio>#include<algorithm>using namespace std;const int maxn = 300010; inline int read(){  int x=0; char ch=getchar();  while (ch<'0' || ch>'9') ch=getchar();  while (ch>='0' && ch<='9'){ x=x*10+ch-'0'; ch=getchar(); }return x;}int c[maxn],n,answer = 0;struct gra{int h,id;bool operator < (const gra &d) const {return h > d.h;}}a[maxn];int lowbit(int x){return x& (-x);}int query(int x){int ans = 0 ;while (x){ans+=c[x] ;x-=lowbit(x);}return ans;}void update(int x,int val){while (x <= n){c[x]+=val;x +=lowbit(x);}}int  main(){n=read();for (int i = 1;i <= n ;i ++){a[i].h=read();a[i].id = i;}sort(a + 1,a + 1 + n);int head=1; long long ans=0;  for (int i=1; i<=n; i++){if (a[i].h!=a[i-1].h)while (head<i) update(a[head++].id , 1);  int tmp=query(a[i].id); ans+=min(tmp,head-1-tmp);}printf("%lld",ans);return 0;} 
  • bzoj 2131 : 免费的馅饼

(数据小点还可以dp的,气)。这个我实在是看的别人的,这里推荐一个写得好的:
http://m.blog.csdn.net/FromATP/article/details/64133191
代码:

#include<cstdio>#include<algorithm>const int size = 100010;using namespace std;int c[size] , n ,w ,y[size],f[size],answ=0,cnt;struct th{    int v,t,loc,w1,w2;    bool operator < (const th &de) const{        if (w1==de.w1) return w2<de.w2;return w1<de.w1;    }}ob[size];int lowbit(int  x){return (x&(-x));}int query(int x){   int ans=0;    while (x)    {ans = max(c[x],ans);    x -= lowbit(x);}    return ans;}void update(int x,int val){   while (x<=n)    {c[x] = max(c[x],val);    x+=lowbit(x);}}int main(){    scanf("%d%d",&w,&n);    for (int i = 1;i<=n;i++){    scanf("%d%d%d",&ob[i].t,&ob[i].loc,&ob[i].v);     ob[i].w1 = 2 * ob[i].t + ob[i].loc;     ob[i].w2 = 2 * ob[i].t - ob[i].loc;     y[++cnt] = ob[i].w2;    }    sort(y + 1 , y + 1 + n);    cnt = unique (y + 1 , y + 1 + n) - y - 1;    for (int i = 1;i<=n;i++) ob[i].w2 = lower_bound(y+1,y+1+cnt,ob[i].w2)-y;    sort(ob + 1,ob + 1 + n);    for (int i = 1;i<=n;i++)    {        f[i] = ob[i].v + query(ob[i].w2);        answ = max(answ,f[i]);        update(ob[i].w2 , f[i]);    }    //for (int i =1;i<=n;i++)    printf("%d",answ);    return 0;}

结语

树状数组大法好,好写又好调!!!

原创粉丝点击