树状数组&离散化

树状数组前置知识:二进制 lowbit的求法
((x^(x-1))&x) = lowbit(x)
可以写成宏,也就是#define lowbit(a) ((a^(a-1))&a)
或者这样也可以找到lowbit(x) = (~x+1)
lowbit的作用:提取最后一个数转换为二进制后的从后往前找第一个出现的1的位置

//
//  main.cpp
//  lowbit
//
//  Created by 陈冉飞 on 2019/8/6.
//  Copyright © 2019 陈冉飞. All rights reserved.
//

#include <iostream>
using namespace std;
#define lowbit(a) ((a^(a-1))&a)

int main(int argc, const char * argv[]) {
    for (int i = 1; i <= 64; i ++) {
    //输出时转换为二进制看从后往前第一个1出现的位置
        cout <<i<<"  "<< bitset<numeric_limits<unsigned int>::digits>(lowbit(i)) <<"  "<< bitset<numeric_limits<unsigned int>::digits>((~i+1))<<endl;
    }
    return 0;
}

在这里插入图片描述
然后就是lowbit在树状数组中的应用的理解
在这里插入图片描述
给定一个奇数长度为9的数组
{4,3,7,5,8,2,6,1,9},然后用树状数组储存,
c 1 = a 1 c_1 = a_1 c1=a1
c 2 = a 1 + a 2 = ∑ a 1 . . . a 2 c_2 = a_1+a_2 = \sum{a_1...a_2} c2=a1+a2=a1...a2
c 3 = a 3 c_3 = a_3 c3=a3
c 4 = a 1 + a 2 + a 3 + a 4 = ∑ a 1 . . . a 4 c_4 = a_1+a_2+a_3+a_4= \sum{a_1...a_4} c4=a1+a2+a3+a4=a1...a4
c 5 = a 5 c_5 = a_5 c5=a5
c 6 = a 5 + a 6 = ∑ a 5 . . . a 6 c_6 = a_5+a_6= \sum{a_5...a_6} c6=a5+a6=a5...a6
c 7 = a 7 c_7 = a_7 c7=a7
c 8 = a 1 + a 2 + a 3 + a 4 + a 5 + a 6 + a 7 + a 8 = ∑ a 1 . . . a 8 c_8 = a_1+a_2+a_3+a_4+a_5+a_6+a_7+a_8= \sum{a_1...a_8} c8=a1+a2+a3+a4+a5+a6+a7+a8=a1...a8
c 9 = a 9 c_9 = a_9 c9=a9

理解:可以理解为 c 2 c_2 c2管理 a 1 . . . a 2 a_1...a_2 a1...a2 c 4 c_4 c4管理 a 1 . . . a 4 a_1...a_4 a1...a4,, c 6 c_6 c6管理 a 5 . . . a 6 a_5...a_6 a5...a6,而 c 8 c_8 c8管理前8个数。

然后结合lowbit发现这个在树形结构中的深度就等于这个数lowbit。

  • 规律表示就是
    x = l o w b i t ( i ) x = lowbit(i) x=lowbit(i)
    c[i] = a[i-x]+a[i-x+1]+……+a[i]
    因而可以用来构造c[i]这个数组

树状数组解题总结:

  • 通常在建树的过程中会有往树形结构中存值,体现在c[i]数组的赋值,c[i]数组的值就是树形结构,在存值过程中通过一个循环遍历
//创建树形结构的过程
while(x <= maxn){
	/*
	 *     对c[x]进行操作
	 *c[x]用来储存某个区间的和:c[x] += num;
	 *c[x]用来储存某个区间的最值:c[x] = max(c[x],num);
	 */
	x += lowbit(x);
}
  • 从树形结构中读取数据,同样利用树状数组c[i]的值来进行赋值。
//利用树形结构查找过程
//传进来的x,是指找到x之前的和
int sum = 0;
while(x>0){
	/*
	 *查找区间的和 sum += c[x];//注意此时是利用树形结构的数据,所以要用c[i]数组要往前遍历,x-=lowbit(x),此时算的就是前缀和
	 */
	x -= lowbit(x);
}	
/*---------------------*/
//传进来的是区间l,r
int ans = 0;
while(l != r){
	//从右边界往左边找,通过比较区间长度与当前l,r的关系
	for(r -= 1; l - r >= lowbit(r); r -= lowbit(r)){
		//比较树状数组储存的最值与当前的这个值的大小
		//刷新最值的操作 ans = max(ans,c[r]);
	}
}

CSU1770
转换思维,对区间进行加一操作。可以利用lowbit对后面的所有区间进行加一,然后还要对不应该加的进行减一操作。最后是否开关灯体现在最后的数组的值来判断是否开闭。

//
//  main.cpp
//  区间修改_csu1770
//
//  Created by 陈冉飞 on 2019/8/6.
//  Copyright © 2019 陈冉飞. All rights reserved.
//

#include <iostream>
using namespace std;
#include <cstring>
#define cl(a,b) memset(a,b,sizeof(a))
#define lowbit(a) (a&(-a))
#define maxn 100050

int a[maxn];
int cnt[maxn];
int n,m;

//单点查找,区间修改,n*logn 的区间修改复杂度
void change(int len,int d){
    while (len <= maxn) {
//        cout<<len<<endl;
        cnt[len] += d;
        len += lowbit(len);
    }
}

int getsum(int x){
    int sum = 0;
    while (x>0) {
        sum += cnt[x];
        x -= lowbit(x);
    }
    return sum;
}

int main(int argc, const char * argv[]) {
    int r,l,tem,i,temcnt;
    while (~scanf("%d%d",&n,&m)) {
        cl(cnt, 0);
        cl(a,0);
        for (i = 1; i <= n; i++) {
            scanf("%d",&tem);
            if (tem == 1) {
                a[i] = 1;
            }else{
                a[i] = 0;
            }
        }
//        cout<<"1"<<endl;
        while (m--) {
//            cout<<"2"<<endl;
            scanf("%d%d",&l,&r);
            //对l到n进行+1修改,对r+1到n进行-1修改
            change(r+1, -1);
            change(l, 1);
//            for (i = 1; i <= n; i++) {
//                printf("%d ",cnt[i]);
//            }
        }
//        for (int i = 1; i <= n; i++) {
//            cout<<i<<"  "<<cnt[i]<<endl;
//        }
        
        //做完所有的操作后,再输出
        for (i = 1; i <= n; i++) {
            temcnt = getsum(i);
            if (temcnt&1) {
                cout<<(a[i]^1);
            }
            else cout<<a[i];
            if (i != n) {
                cout<<" ";
            }
            else cout<<endl;
//            if (temcnt%2 == 1||temcnt%2 == -1) {
//                cout<<"1 ";
//            }else cout<<"0 ";
//            printf("%d ",cnt[i]);
        }
    }
    return 0;
}
//5 4 1 0 1 1 1 1 3 1 2 2 3 1 5
//0 0 0 0 0

hdu1754
通过一个m[]数组来记录子区间长度为lowbit®的区间范围的最大值,在改值的时候也要更新最大值。但注意原数组要一直保持是原来的数,cnt用来记录区间的最值。

//
//  main.cpp
//  树状数组_板题_hdu1754
//
//  Created by 陈冉飞 on 2019/8/6.
//  Copyright © 2019 陈冉飞. All rights reserved.
//

#include <iostream>
//#include <bits/stdc++.h>
using namespace std;
#include <cstring>
#define cl(a,b) memset(a,b,sizeof(a))
typedef long long ll;
#define lowbit(a) (a&(a^(a-1)))
#define maxn 200050
int a[maxn];  //记录原来的
int m[maxn];  //记录最大值
int n,mm;   //共有多少个元素   m为所有请求的个数

int getmax(int l,int r){
    //范围最值是通过比较时时的区间长度,而在树状数组中自区间的长度是有lowbit决定的,所以一定要在又端点去变化
    int ans = a[r];
    while(l!=r){
        for (r-=1; r-l >= lowbit(r); r -= lowbit(r)) {
            ans = max(ans,m[r]);
        }
        ans = max(ans,a[r]);
    }
    return ans;
}

void add(int x,int y){
    //更新值
    a[x] = y;
    while (x <= maxn) {
        //然后通过lowbit 遍历到的都刷新最值
        m[x] = max(y,m[x]);
        x += lowbit(x);
    }
}

int main(int argc, const char * argv[]) {
    int T,i,tem,tema,temb,kase = 1;
//    scanf("%d",&T);
    char str[10];
    while (~scanf("%d%d",&n,&mm)) {
//        cout<<"Case "<<kase++<<":"<<endl;
        cl(a, 0);
        cl(m, 0);
        for (i = 1; i <= n; i++) {
            scanf("%d",&tem);
            //通过add 数组将树状数组c放好
            add(i, tem);
        }
        //        //检验输出
        //        for (i = 2; i <= n; i++) {
        //            cout<<i<<"  "<<a[i]<<"  "<<sum(i)-sum(i-1)<<endl;
        //        }
        while(mm--){
            scanf("%s",str);
            if (str[0] == 'Q') {      //查询区间最大值
                scanf("%d%d",&tema,&temb);
                cout<<getmax(tema, temb)<<endl;
            }else{
                scanf("%d%d",&tema,&temb);
                add(tema, temb);
            }
//            //每次都检验输出
//            cout<<"check  "<<n<<endl;
//            for (int j = 1; j <= n; j ++) {
//                cout<<j<<"  "<<a[j]<<"  "<<m[j]<<endl;
//            }
        }
    }
    return 0;
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值