树状数组大杂烩

一、引言

  作为胸牌退役狗决定再搞一发noip=。=

  今天遇到好几个有关差分的暴力解法(noip就是要暴力!!!),又想起之前有个树状数组的区间修改&区间查询很玄学的表现为正确,决定想办法证明一发,但是内容单调不是我的性格,然后就有了这个大杂烩。。。(XX:树状数组被玩坏的日常)

  本文借鉴了以下博文的部分内容,在此表示感谢分享交流,如有遗漏还请见谅(毕竟有的是一年前蒯的= =):

  http://blog.csdn.net/qq_21841245/article/details/43956633(关于差分的思路)

  (还有一个影响最大的找不到了,如果这位朋友发现本文有您的原创内容请评论联系我= =)

二、基础部分 (非初学者请自行忽略= =)

  1、树状数组的正确性保证

    树状数组之所以正确,在于问题中修改集合与查询集合的关系,我们需要保证修改的部分在查询时只被计算一次。

    也就是说,对于某次修改,被修改的值的集合与查询这次修改时的查询集合的交集有且仅有一个元素。

    而树状数组是通过每次将所在前缀末端在序列中rank值的二进制数,从低位向高位依次将1变为0,过程中出现的二进制数对应序列中rank值所在位置进行修改。

    以点修改为例,修改某点的值,相当于将这个点的位置和后面的位置的前缀和进行了修改,所以将它依次将rank值的二进制数末尾的0变为1(从小到大)修改,然后查询某点位置的前缀和时将rank值的二进制数末尾位置的1进位(从大到小)求该前缀被修改的量,与初始前缀和相加即可(PS:其实赋初值时也可以看作对一个初始元素全部为0的序列的单点修改)。

    上一段话中,若查询位置rank小于修改位置,则两者的方向是背道而驰的,不可能有交集;只有查询位置rank大于修改位置时,两者才可能有交集,由于我们是从二进制数的低位向高位变化,所以不存在查询位置rank将所有修改位置跳过,因为此时的查询位置rank较大,肯定有且仅有一次进位使得两集合同时存在这个元素。

  2、单点修改+区间查询

    这是树状数组最纯洁的时候的用法= =

    1中说的将末尾的1进位和消去有个很简单的方法,利用C++补码的性质,对于对于一个位置 i 需要“跳”的距离为 lowbit = i & (-i)

    也是正确性保证时证明的东西,直接贴代码:

 1 void add(int x,int c){//修改位置x的值加上c
 2     for(int i = x;i <= n;i += i&(-i))
 3         a[i] += c;
 4 }
 5
 6 int sum(int x){//查询1~x的和
 7     int ret = 0;
 8     for(int i = x;i > 0;i -= i&(-i))
 9         ret += a[i];
10     return ret;
11 }

  3、区间修改+单点查询

    其实与上述方式一样,点修改时我们考虑到对后续部分前缀和的影响,那么我们在段修改(前缀修改)时只需考虑对前面部分点的值的影响。

 1 void add(int x,int c){//1~x每个数加上c
 2     for(int i = x;i > 0;i -= i&(-i))
 3         b[i] += c;
 4 }
 5
 6 int sum(int x){//查询位置x的值
 7     int ret = 0;
 8     for(int i = x;i <= n;i += i&(-i))
 9         ret += b[i];
10     return ret;
11 }

三、正文:区间修改+区间查询 如何玩坏它= =

  1、差分思想

    引入数组d[i]表示 [i,n] 的共同增量,利用差分的思想可以发现,对于修改 [l,r] 只需修改d[l]和d[r+1],以下代码来自文首链接:

 1 /*
 2 作者:Airy
 3 题目:p1082 线段树练习 3
 4 */
 5
 6 #include <cstdio>
 7 #include <iostream>
 8
 9 #define lowbit(i) (i & (-i))
10
11 using namespace std;
12
13 int readint()
14 {
15     int sign = 1, n = 0; char c = getchar();
16     while(c < ‘0‘ || c > ‘9‘){ if(c == ‘-‘) sign = -1; c = getchar(); }
17     while(c >= ‘0‘ && c <= ‘9‘) { n = n*10 + c-‘0‘; c = getchar(); }
18     return sign*n;
19 }
20
21 const int Nmax = 200100;
22
23 int N, Q;
24
25 long long delta[Nmax]; // delta的前缀和
26 long long deltai[Nmax]; // delta * i的前缀和
27 long long sum[Nmax]; // 原始前缀和
28
29 long long Query(long long *array, int pos)
30 {
31     long long temp = 0ll;
32     while(pos > 0)
33     {
34         temp += array[pos];
35         pos -= lowbit(pos);
36     }
37     return temp;
38 }
39
40 void Update(long long *array, int pos, int x)
41 {
42     while(pos <= N)
43     {
44         array[pos] += x;
45         pos += lowbit(pos);
46     }
47 }
48
49 int main()
50 {
51     N = readint();
52
53     for(int i = 1; i <= N; ++i)
54     {
55         int x = readint();
56         sum[i] = sum[i - 1] + x;
57     }
58
59     Q = readint();
60
61     while(Q--)
62     {
63         int sign = readint();
64         if(sign == 1) // 修改:把[l, r]区间均加上x
65         {
66             int l = readint(), r = readint(), x = readint();
67             Update(delta, l, x);
68             Update(delta, r+1, -x);
69             Update(deltai, l, x * l);
70             Update(deltai, r+1, -x * (r+1));
71         }
72         else // 查询:[l, r]区间和
73         {
74             int l = readint(), r = readint();
75             long long suml = sum[l - 1] + l * Query(delta, l - 1) - Query(deltai, l - 1);
76             long long sumr = sum[r] + (r + 1) * Query(delta, r) - Query(deltai, r);
77             printf("%lld\n", sumr - suml);
78         }
79     }
80
81     return 0;
82 }  

    虽说风格不同,但还是很好看出其中的分析的。。。(代码中deltai[]即d数组)

  2、表示自己还没搞懂,应该和上面差不多,自认为有点玄学

    直接上代码,这个背背更健康:

 1 void add_a(int x,int c){
 2     for(int i = x;i > 0;i -= i&(-i))
 3         a[i] += c;
 4 }
 5
 6 int sum_a(int x){
 7     int ret = 0;
 8     for(int i = x;i <= n;i += i&(-i))
 9         ret += a[i];
10     return ret;
11 }
12
13 void add_b(int x,int c){
14     for(int i = x;i <= n;i += i&(-i))
15         b[i] += c * x;
16 }
17
18 int sum_b(int x){
19     int ret = 0;
20     for(int i = x;i > 0;i -= i&(-i))
21         ret += b[i];
22     return ret;
23 }
24
25 void add(int l,int r,int c){
26     add_a(r,c),add_b(r,c);
27     if(l > 1){
28         add_a(l-1,-c);
29         add_b(l-1,-c);
30     }
31 }
32
33 void sum(int x){//这个是1~x的前缀和
34     if(x)
35         return sum_a(x) * x + sum_b(x-1);
36     else
37         return 0;
38 }

四、结语

  呃,也没什么好说的了,给自己一句祝福:while(1){rp++;}  

时间: 2024-10-30 10:33:50

树状数组大杂烩的相关文章

HDU 5542 The Battle of Chibi dp+树状数组

题目:http://acm.hdu.edu.cn/showproblem.php?pid=5542 题意:给你n个数,求其中上升子序列长度为m的个数 可以考虑用dp[i][j]表示以a[i]结尾的长度为j的上升子序列有多少 裸的dp是o(n2m) 所以需要优化 我们可以发现dp的第3维是找比它小的数,那么就可以用树状数组来找 这样就可以降低复杂度 #include<iostream> #include<cstdio> #include<cstring> #include

(POJ 3067) Japan (慢慢熟悉的树状数组)

Japan Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 29295   Accepted: 7902 Description Japan plans to welcome the ACM ICPC World Finals and a lot of roads must be built for the venue. Japan is tall island with N cities on the East coas

【二维树状数组】See you~

https://www.bnuoj.com/v3/contest_show.php?cid=9148#problem/F [题意] 给定一个矩阵,每个格子的初始值为1.现在可以对矩阵有四种操作: A x y n1 :给格点(x,y)的值加n1 D x y n1: 给格点(x,y)的值减n1,如果现在格点的值不够n1,把格点置0 M x1 y1 x2 y2:(x1,y1)移动给(x2,y2)n1个 S x1 y1 x2 y2 查询子矩阵的和 [思路] 当然是二维树状数组 但是一定要注意:lowbi

Vijos P1066 弱弱的战壕【多解,线段树,暴力,树状数组】

弱弱的战壕 描述 永恒和mx正在玩一个即时战略游戏,名字嘛~~~~~~恕本人记性不好,忘了-_-b. mx在他的基地附近建立了n个战壕,每个战壕都是一个独立的作战单位,射程可以达到无限(“mx不赢定了?!?”永恒[email protected][email protected]). 但是,战壕有一个弱点,就是只能攻击它的左下方,说白了就是横纵坐标都不大于它的点(mx:“我的战壕为什么这么菜”ToT).这样,永恒就可以从别的地方进攻摧毁战壕,从而消灭mx的部队. 战壕都有一个保护范围,同它的攻击

CF 313 DIV2 B 树状数组

http://codeforces.com/contest/313/problem/B 题目大意 给一个区间,问你这个区间里面有几个连续相同的字符. 思路: 表示个人用树状数组来写的...了解了树状数组的本质就行了. 当然用sum[r]-sum[l]也是可以的

Hdu5032 极角排序+树状数组

题目链接 思路:参考了题解.对询问进行极角排序,然后用树状数组维护一下前缀和即可. /* ID: onlyazh1 LANG: C++ TASK: test */ #include<bits/stdc++.h> using namespace std; #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 typedef long long ll; const int maxn=1010; const int maxm=10

Curious Robin Hood(树状数组+线段树)

1112 - Curious Robin Hood    PDF (English) Statistics Forum Time Limit: 1 second(s) Memory Limit: 64 MB Robin Hood likes to loot rich people since he helps the poor people with this money. Instead of keeping all the money together he does another tri

【初识——树状数组】 区间求最值

说树状数组其实是一个索引表,但是是一个特殊的,树状的索引表,它利用了二进制的一些特性. 就区间求和的要求来说: 首先我们用a[]数组来存储原始数据.然后在a[]之上构造c[]数组来作为树状数组. 如图 这个图表示,当i为奇数时,c[i]中保存的都是a[i]本身.然后,c[2]中保存了a[1], a[2],共2个,c[4]中保存的是a[1], a[2], a[3], a[4],c[6]又是保存两个,c[5]和c[6].c[8]保存8个,c[1], c[2], c[3], c[4], c[5], c

树状数组求区间最大值

------  一直用 线段树 求区间最大值,想换种思路,用树状数组试试,肯定是可以的. 首先要对 树状数组的每个 i 所管理的区间有一定的理解.详见上篇博客: 树状数组(BIT)