树状数组

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

树状数组(二叉搜索树)

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

题目: 略

题解:水题啊,注意到 c 的值很小,m n 的也很小,那么我们就记录树状数组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;
}


题目:
题解:
(这题好啊)*最小交换次数就是最后序列的关于原位置的逆序对*,然后我们可以证明最后数列是一个先增后减的样子。(好不容易遇到一个我会证的,当然要证一证)。如下:首先最大的可以随便放,然后我们考虑他左边的第一个数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;} 


(数据小点还可以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;
}


结语

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值