• 树状数组


    树状数组

    1. 算法分析

    树状数组作用

    1. 单点修改
    2. 区间查询
    3. 区间修改(加上差分)
      批注 2020-06-08 001350.png

    核心思想
        把前n个数划分为log(n)个区间,分别维护这log(n)个区间的和,在求解前缀和S~n~的时候,从求解n个数字的和变成求解log(n)个区间的和来加快运算

    具体操作
        维护log(n)个区间,每个区间用数组c来维护区间和。单点修改x的时候,修改x所在的c,然后一路向上修改父节点的c;区间查询[1 ~ x]的时候,从x开始向前,把x的所有兄弟节点的c都加起来

    c数组性质

    1. c[x]数组维护区间[x - lowbit(x) + 1, x]的和
    2. c[x]数组维护的区间的长度为lowbit(x)
    3. c[x]的父节点为 c[x + lowbit(x)]
    4. c[x]前一个兄弟节点为 c[x - lowbit(x)]

    2. 板子

    2.1 一维树状数组:单点修改+区间查询

    #include <bits/stdc++.h>
    
    using namespace std;
    
    typedef long long LL;
    int const N = 5e5 + 10;
    int a[N], n, m;
    LL c[N], sum[N];  // c[x] = a[x-lowbit(x) + 1] + ... + a[x],sum[x]=a[1]+...+a[x]
    
    int lowbit(int x) {
        return x & (-x);
    }
    
    // 单点修改
    void add(int x, int y) {
        for (int i = x; i <= n; i += lowbit(i)) c[i] += y;  // 不断往父节点跳
    }
    
    // 查询Sx前缀和
    LL query(int x) {
        LL res = 0;
        for (int i = x; i; i -= lowbit(i)) res += c[i];  // 不断往前一个兄弟节点跳
        return res;
    }
    
    int main() {
        cin >> n >> m;
        for (int i = 1; i <= n; ++i) {
            scanf("%d", &a[i]);  // 读入每个元素
            sum[i] = sum[i - 1] + a[i];  // 计算前缀和
        }
        
        for (int i = 1; i <= n; ++i) c[i] = sum[i] - sum[i - lowbit(i)];  // 初始化c数组
        
        for (int i = 1, op, x, y; i <= m; ++i) {  // m个操作
            scanf("%d%d%d", &op, &x, &y);  
            if (op == 1) add(x, y);   // 单点修改
            else printf("%lld
    ", query(y) - query(x - 1));  // 区间查询
        }
        
        return 0;
    }
    

    2.2 一维树状数组:区间修改+单点查询

    /*
    本题要进行的是 区间增加+单点询问,而树状数组能进行的操作为 单点增加 + 区间查询
    可以通过差分来实现这个转变,使得树状数组可以完成区间增加 + 单点询问
    即:维护一个b数组表示当前a数组的增加减少情况,一旦a数组[l, r]增加d, 那么b[l] += d, b[r + 1] -= d
    因此我们可以用树状数组维护这个b数组的前缀和,即维护一个c数组,记录b数组的前缀和,那么通过add()操作就能改变前缀和
    而后每次询问单点时,我们查询b数组的前缀和就可以知道a点的增加减少情况
    */
    
    
    #include<bits/stdc++.h>
    using namespace std;
    
    typedef long long LL;
    
    int n, m;
    int const N = 1e5 + 10;
    int c[N], a[N];  // c[x]维护的是b数组的前缀和
    
    int lowbit(int x) {
        return x & -x;
    }
    
    // 单点修改
    void add(int x, int y) {
        for (int i = x; i <= n; i += lowbit(i)) c[i] += y;  // 不断往父节点跳
    }
    
    // 查询Sx前缀和
    LL query(int x) {
        LL res = 0;
        for (int i = x; i; i -= lowbit(i)) res += c[i];  // 不断往前一个兄弟节点跳
        return res;
    }
    
    int main()
    {
        cin >> n >> m;
        for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);  // 读入a[i]
        while (m--) {
            string op;
            int l, r, d, x;
            cin >> op;
    
            // 单点查询
            if (op[0] == 'Q') {
                scanf("%d", &x);
                printf("%lld
    ", query(x) + a[x]);  // x点的变化值+a[x]
            }
            else {
                scanf("%d %d %d", &l, &r, &d);
                add(l, d), add(r + 1, -d);  // 区间增加
            }
        }
        return 0;
    }
    

    2.3 一维树状数组:区间修改+区间查询

    /*
    对于第一种处理方式,可以利用差分结合acwing242一个简单的整数问题1的方式来处理;
    对于问题而求区间和,那么考虑维护a的前缀和
    a1+a2+...+ax=(b1)+(b1+b2)+(b1+b2+b3)+...+(b1+b2+b3+..+bx)
    =(1+x)累加从1到x(bi)-累加从1到x(i*bi)
    我们可以使用两个树状数组分别维护bi和i*bi的前缀和
    */
    #include<bits/stdc++.h>
    using namespace std;
    
    typedef long long LL;
    
    int n, m;
    int const N = 1e5 + 10;
    LL c1[N], c2[N];  // c1维护bi的前缀和,c2维护i*bi的前缀和
    int a[N];
    LL s[N];
    
    int lowbit(int x) {
        return x & -x;
    }
    
    // 单点修改
    void add(LL c[], int x, int y) {
        for (int i = x; i <= n; i += lowbit(i)) c[i] += (LL)y;
    }
    
    // 前缀和
    LL query(LL c[], int x) {
        LL res = 0;
        for (int i = x; i; i -= lowbit(i)) res += c[i];
        return res;
    }
    
    int main() {
        cin >> n >> m;
        for (int i = 1; i <= n; ++i) {
            scanf("%d", &a[i]);
            s[i] = s[i - 1] + 0ll + a[i];  // 计算原来的a[i]的前缀和
        }
        while (m--) {
            string op;
            int l, r, d;
            cin >> op;
            if (op[0] == 'C') {
                cin >> l >> r >> d;
                add(c1, l, d), add(c1, r + 1, -d);  // 把[l, r]加d对c1来说就是把l和r+1分别增加d和减去d
                add(c2, l, l * d), add(c2, r + 1, -(r + 1) * d);  // 把[l, r]加d对c1来说就是把l和r+1分别增加l*d和减去(r+1)*d
            }
            else {
                cin >> l >> r;
                LL res = s[r] - s[l - 1];  // 原来的区间和
                res += ((1 + r) * query(c1, r) - query(c2, r) - (l * query(c1, l - 1) - query(c2, l - 1)));  // 加上改变值
                printf("%lld
    ", res );
            }
        }
        return 0;
    }
    

    2.4 二维树状数组:单点修改+区间查询

    #include <bits/stdc++.h>
    
    using namespace std;
    
    typedef long long LL;
    
    int const N = (1 << 12) + 10;
    LL n, m, c[N][N];  // c[x][y] = 累加c[i][j], i∈(i-lowbit(i)+1, x), j∈(j-lowbit(j)+1, y) 
    
    int lowbit(int x) {
        return x & -x;
    }
    
    // 单点修改
    void add(int x, int y, int z) {
        for (int i = x; i <= n; i += lowbit(i)) 
            for (int j = y; j <= m; j += lowbit(j)) 
                c[i][j] += (LL)z;
    }
    
    // 区间查询
    LL query(int x, int y) {
        LL res = 0;
        for (int i = x; i; i -= lowbit(i)) 
            for (int j = y; j; j -= lowbit(j)) 
                res += c[i][j];
        return res;
    }
    
    int main() {
        cin >> n >> m;
        int op, x1, y1, k, x2, y2;
        while (scanf("%d", &op) != EOF) {
            if (op == 1) {  // 单点修改,把(x1, y1)加上k
                scanf("%d%d%d", &x1, &y1, &k);
                add(x1, y1, k);  
            }
            else {  // 区间查询,得到以左上角为(x1, y1),右下角为(x2, y2)的矩形的区间和
                scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
                printf("%lld
    ", query(x2, y2) - query(x2, y1 - 1) - query(x1 - 1, y2) + query(x1 - 1, y1 - 1));
            }
        }
        return 0;
    }
    

    2.5 二维树状数组:区间修改+单点查询

    #include <bits/stdc++.h>
    
    using namespace std;
    
    typedef long long LL;
    int const N = (1 << 12) + 10;
    LL c[N][N], n, m, op;  // c[x][y]维护b[i][j]的前缀和
    
    int lowbit(int x) {
        return x & (-x);
    }
    
    // 二维单点修改
    void add(int x, int y, int z) {
        for (int i = x; i <= n; i += lowbit(i)) 
            for (int j = y; j <= m; j += lowbit(j))
                c[i][j] += z;
    }
    
    // 求二维前缀和
    LL query(int x, int y) {
        LL res = 0;
        for (int i = x; i; i -= lowbit(i))
            for (int j = y; j; j -= lowbit(j))
                res += c[i][j];
        return res;
    }
    
    int main() {
        cin >> n >> m;
        while (scanf("%lld", &op) != EOF) {
            int x1, y1, k, x2, y2;
            if (op == 1) {  // 区间增加:转化为二维差分数组的增加
                scanf("%d%d%d%d%d", &x1, &y1, &x2, &y2, & k);
                add(x2 + 1, y2 + 1, k);
                add(x1, y1, k);
                add(x2 + 1, y1, -k);
                add(x1, y2 + 1, -k);
            }
            else {  // 单点查询:转化为二维差分数组求前缀和
                scanf("%d%d", &x1, &y1);
                printf("%lld
    ", query(x1, y1));
            }
        }
        return 0;
    }
    

    2.6 二维树状数组:区间修改+区间查询

    /*
    累加aij(i∈[1, x], j∈[1, y])=(x+1)*(y+1)累加bij - (x+1)累加j*bij - (y + 1)累加i*bij + 累加i*j*bij
    c1维护bij的二维前缀和,c2维护j*bij的二维前缀和,c3维护i*bij的二维前缀和,c4维护i*j*bij的二维前缀和
    */
    #include <bits/stdc++.h>
    
    using namespace std;
    
    typedef long long LL;
    int const N = (1 << 12) + 10;
    LL c1[N][N], c2[N][N], c3[N][N], c4[N][N], n, m, op;  // c[x][y]维护b[i][j]的前缀和
    
    int lowbit(int x) {
        return x & (-x);
    }
    
    // 二维单点修改
    void add(LL c[][N], int x, int y, LL z) {
        for (int i = x; i <= n; i += lowbit(i)) 
            for (int j = y; j <= m; j += lowbit(j))
                c[i][j] += z;
    }
    
    // 求二维前缀和
    LL query(LL c[][N], int x, int y) {
        LL res = 0;
        for (int i = x; i; i -= lowbit(i))
            for (int j = y; j; j -= lowbit(j))
                res += c[i][j];
        return res;
    }
    
    LL Query(int x, int y) {
        return (x + 1) * (y + 1) * query(c1, x, y) - (x + 1) * query(c2, x, y) - (y + 1) * query(c3, x, y) + query(c4, x, y);
    }
    
    int main() {
        cin >> n >> m;
        while (scanf("%lld", &op) != EOF) {
            int x1, y1, x2, y2, k;
            if (op == 1) {
                scanf("%d%d%d%d%d", &x1, &y1, &x2, &y2, &k);
                add(c1, x2 + 1, y2 + 1, k), add(c1, x1, y1, k), add(c1, x2 + 1, y1, -k), add(c1, x1, y2 + 1, -k);
                add(c2, x2 + 1, y2 + 1, k * (y2 + 1)), add(c2, x1, y1, k * y1), add(c2, x2 + 1, y1, -k * y1), add(c2, x1, y2 + 1, -k * (y2 + 1));
                add(c3, x2 + 1, y2 + 1, k * (x2 + 1)), add(c3, x1, y1, k * x1), add(c3, x2 + 1, y1, -k * (x2 + 1)), add(c3, x1, y2 + 1, -k * x1);
                add(c4, x2 + 1, y2 + 1, k * (x2 + 1) * (y2 + 1)), add(c4, x1, y1, k * x1 * y1), add(c4, x2 + 1, y1, -k * (x2 + 1) * y1), add(c4, x1, y2 + 1, -k * x1 * (y2 + 1));
            }
            else {
                scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
                printf("%lld
    ", Query(x2, y2) + Query(x1 - 1, y1 - 1) - Query(x1 - 1, y2) - Query(x2, y1 - 1));
            }
        }
        return 0;
    }
    

    3. 例题

    3.1 树状数组+逆序对

    POJ2299 Ultra-QuickSort
    有多个测试样例,每个测试样例给定n个数字a[i], 求这n个数字的逆序对数目
    n~5e5, a[i]~999999999

    // 使用树状数组去维护每个数字出现的次数的前缀和,c[i]记录i这个节点管辖的几个点的出现次数
    // 统计时,只要统计每个点右边有多少个比他小即可
    // 注意需要离散化
    #include <bits/stdc++.h>
    
    using namespace std;
    typedef long long LL;
    int const N = 5e5 + 10;
    LL c[N], n, a[N];
    vector<LL> v;
    unordered_map<LL, int> H;
    
    int lowbit(int x) {
        return x & -x;
    }
    
    void add(int x, int y) {
        for (int i = x; i <= n; i += lowbit(i)) c[i] += y;
    }
    
    LL query(int x) {
        LL res = 0;
        for (int i = x; i; i -= lowbit(i)) res += c[i];
        return res;
    }
    
    int main() {
        while (scanf("%lld", &n) != EOF && n) {
            memset(c, 0, sizeof c);
            v.clear();
            H.clear();
            for (int i = 1; i <= n; ++i) {
                scanf("%lld", &a[i]);
                v.push_back(a[i]);
            }
            sort(v.begin(), v.end());
            int cnt = 1;
            for (int i = 0; i < v.size(); ++i) H[v[i]] = cnt++;  // 离散化
            LL res = 0;
            for (int i = n; i >= 1; --i) {  // 计算每个H[a[i]出现的次数
                res += query(H[a[i]] - 1);  // 加上H[a[i]-1出现的总次数
                add(H[a[i]], 1);  // 次数加一
            }
            printf("%lld
    ", res);
        }
        return 0;
    }
    

    acwing241楼兰图腾
    有n个点,坐标分别为(1, y1), (2, y2), (3, y3), ..., (n, yn)
    如果三个点(i, yi), (j, yj), (k, yk) 满足1 ≤ i < j < k ≤ n且yi > yj, yj < yk,则称这三个点构成V图腾;
    如果三个点(i, yi), (j, yj), (k, yk)满足1 ≤ i < j < k ≤ n 且yi < yj, yj > yk,则称这三个点构成∧图腾;
    求有多少个V图腾和∧图腾
    n ~ 2e5, yi ~ 2e5

    /*
    使用树状数组去维护每个数字出现的次数的前缀和,c[i]记录i这个节点管辖的几个点的出现次数
    统计时,只要统计每个点左边有多少个比他大,右边有多少个比他大,然后相乘就能够知道出现多少个^;
    统计V则相反
    */
    #include<bits/stdc++.h>
    using namespace std;
    
    typedef long long LL;
    
    int n;
    int const N = 2e5 + 10;
    int a[N], c[N];
    int gre[N], low[N];
    
    int lowbit(int x) {
        return x & -x;
    }
    
    void add(int x, int y) {
        for (int i = x; i <= n; i += lowbit(i)) c[i] += y;
    }
    
    int query(int x) {
        int res = 0;
        for (int i = x; i; i -= lowbit(i)) res += c[i];
        return res;
    }
    
    int main() {
        cin >> n;
        for (int i = 0; i < n; ++i) scanf("%d", &a[i]);
        LL res1 = 0, res2 = 0;
        for (int i = 0; i < n; ++i) {
            int y = a[i];
            gre[i] = query(n) - query(y);
            low[i] = query(y - 1);
            add(y , 1);
        }
    
        memset(c, 0, sizeof c);
        for (int i = n - 1; i >= 0; --i) {
            int y = a[i];
            res1 += (LL)gre[i] * (query(n) - query(y));
            res2 += (LL)low[i] * query(y - 1);
            add(y , 1);
        }
        cout << res1 << " " << res2 << endl;
        return 0;
    }
    

    POJ3067 Japan
    有两个海岸,两个海岸分别有N个点和M个点,从北到南编号为1,2,3...,N。现在在两个海岸之间建设桥梁。每个桥梁给出x, y,表示第一个海岸的x点到第二个海岸的y点有桥梁。问形成交叉的桥梁数目有多少。
    N、M~1e3

    // 设桥梁1为(x1, y1),桥梁2为(x2, y2),当(x1-x2)*(y1-y2)<0时两个桥梁交叉
    // 因此只需要把y按照从大到小的顺序排序,每次观察小于当前x的数有多少个即可
    // 这样就转化为求解逆序对的问题,树状数组求之
    #include<bits/stdc++.h>
    using namespace std;
    
    typedef long long LL;
    
    int n, m, T, q, kase = 1;
    int const N = 1e3 + 10;
    LL c[N];
    struct Line {
        int x, y;
        bool operator<(const struct Line &w) const {
            if (y == w.y) return x > w.x;
            else return y > w.y;
        }
    }line[N * N];
    
    LL lowbit(LL x) {
        return x & -x;
    }
    
    // 单点修改
    void add(int x, LL y) {
        for (int i = x; i <= N; i += lowbit(i)) c[i] += y;  // 不断往父节点跳
    }
    
    // 查询Sx前缀和
    LL query(int x) {
        LL res = 0;
        for (int i = x; i; i -= lowbit(i)) res += c[i];  // 不断往前一个兄弟节点跳
        return res;
    }
    
    int main()
    {
        cin >> T;
        while (T--) {
            cin >> n >> m >> q;
            memset(c, 0, sizeof c);
            for (int i = 1; i <= q; ++i) scanf("%d%d", &line[i].x, &line[i].y);
            sort(line + 1, line + 1 + q);  // 按y从大到小排序
            
            LL res = 0;
            for (int i = 1; i <= q; ++i) {
                res += query(line[i].x - 1);  // 计算小于当前x的数目
                add(line[i].x, 1);  // 当前x加一
            }
            printf("Test case %d: %lld
    ", kase++, res);
        }
        return 0;
    }
    

    POJ 2352 Stars
    给一个二维网格,然后给定网格上n个点,计算每个点的左下方的点个数
    点的输入是y从小到大输入,如果y相同,那么x从小到大输入。
    n~15000, x、y~32000

    // 题目顺序都处理好了。注意向右平移一个单位,因为0号位置也有star
    #include <bits/stdc++.h>
    
    using namespace std;
    
    typedef long long LL;
    int const N = 2e5 + 10;
    int n;
    LL c[N], res[N];
    
    int lowbit(int x) {
        return x & (-x);
    }
    
    // 单点修改
    void add(int x, int y) {
        for (int i = x; i <= N; i += lowbit(i)) c[i] += y;  // 不断往父节点跳
    }
    
    // 查询Sx前缀和
    LL query(int x) {
        LL res = 0;
        for (int i = x; i; i -= lowbit(i)) res += c[i];  // 不断往前一个兄弟节点跳
        return res;
    }
    
    int main() {
        cin >> n;
        for (int i = 1, x, y; i <= n; ++i) {
            scanf("%d%d", &x, &y);
            x++;  // 树状数组不能统计0
            res[query(x)]++;  // 计数
            add(x, 1);  //x的次数加一
        }
        for (int i = 0; i <= n - 1; ++i) printf("%lld
    ", res[i]);
        
        return 0;
    }
    

    3.2 树状数组+思维

    acwing244谜一样的牛
    有n头奶牛,已知它们的身高为 1~n 且各不相同,但不知道每头奶牛的具体身高。
    现在这n头奶牛站成一列,已知第i头牛前面有Ai头牛比它低,求每头奶牛的身高。
    1≤n≤10^5^

    /*
    本题可以从n往1向前看,如果他的前面有a[i]头牛比他低,那第i头牛的高度就是可以选择的身高的第i+1小。
    因此可以给每个身高一个标值,1表示没被选,0表示被选中。那么寻找第a[i]+1个没被选中的牛就可以二分查找
    前缀和等于a[i]+1的那个数字,树状数组维护这个前缀和即可
    */
    #include<bits/stdc++.h>
    
    using namespace std;
    
    int n;
    int const N = 1e5 + 10;
    int c[N], a[N], ans[N];
    
    int lowbit(int x) {
        return x & -x;
    }
    
    void add(int x, int y) {
        for (int i = x; i <= n; i += lowbit(i)) c[i] += y;
    }
    
    int query(int x) {
        int res = 0;
        for (int i = x; i; i -= lowbit(i)) res += c[i];
        return res;
    }
    
    int main()
    {
        cin >> n;
        for (int i = 2; i <= n; ++i) scanf("%d", &a[i]);
    
        for (int i = 1; i <= n; ++i) c[i] = lowbit(i);  // 初始化,因为每个身高都没备选,那么都初始化为1,Tr数组就是区间的长度,即为lowbit(i)
    
        for (int i = n; i >= 1; --i)   // 倒着往前看
        {
            // 二分查找a[i]+1小
            int l = 1, r = n;  
            while (l < r)
            {
                int mid = (l + r) >> 1;
                if (query(mid) >= a[i] + 1) r = mid;
                else l = mid + 1;
            }
            ans[i] = l;  // 记录答案 
            add(l, -1);  // 这个牛被选了,从1变成0
        }
        for (int i = 1; i <= n; ++i) printf("%d
    ", ans[i]);
        return 0;
    }
    

    acwing260买票
    给定n,表示游客的数目;随后n行,每行两个数,p[i]和v[i],分别表示当这个游客插队后,前面的人数以及这个游客的编号。求出当全部游客完成插队后,队列中的游客编号情况。
    n ~ 2e5,P[i]、V[i] ~ short

    // 本题的思路和acwing244一样,倒着往前看,插入前面人数+1的位置。
    #include <bits/stdc++.h>
    
    using namespace std;
    
    int const N = 2e5 + 10;
    int res[N], P[N], V[N], n, c[N];
    
    typedef long long LL;
    
    int lowbit(int x) {
        return x & -x;
    }
    
    void add(int x, int y) {
        for (int i = x; i <= n; i += lowbit(i)) c[i] += y;
    }
    
    int query(int x) {
        int res = 0;
        for (int i = x; i; i -= lowbit(i)) res += c[i];
        return res;
    }
    
    int main() {
        while (scanf("%d", &n) != EOF) {
            for (int i = 1; i <= n; ++i) {
                scanf("%d%d", &P[i], &V[i]);
                c[i] = lowbit(i);
            }
            for (int i = n; i >= 1; --i) {
                int pos = P[i] + 1;
                int l = 1, r = n;
                while (l < r) {
                    int mid = l + r >> 1;
                    if (query(mid) >= pos) r = mid;
                    else l = mid + 1;
                }
                res[l] = V[i];
                add(l, -1);
            }
            
            for (int i = 1; i <= n; ++i) printf("%d ", res[i]);
            cout << endl;
        }
        return 0;
    }
    

    Vijos P1448校门外的树
    校门外有很多树,有苹果树,香蕉树,有会扔石头的,有可以吃掉补充体力的……如今学校决定在某个时刻在某一段种上一种树,保证任一时刻不会出现两段相同种类的树,现有两个操作:
    K=1,读入l、r表示在区间[l,r]中种上一种树,每次操作种的树的种类都不同
    K=2,读入l,r表示询问l~r之间能见到多少种树
    (l,r>0)
    校门长度n,询问次数m<=50000

    /* 左右括号的方法。在一个区间内种树,相当于加一对括号。用树状数组维护从起始到这个位置的左右括号的数量。
    区间内有左括号那么一定有这一种类型的树,只有离开了对应的右括号这种树才没有了。
    所以为了统计区间[x,y]内的树种类,只需把y左边左括号的个数-(x-1)左边右括号的个数即可。 */
    #include <bits/stdc++.h>
    
    using namespace std;
    
    int const N = 5e4 + 10;
    int c1[N], c2[N], n, m;  // c1[x]维护左括号的数目,c2[x]维护右括号的数目
    
    int lowbit(int x) {
        return x & -x;
    }
    
    void add(int c[], int x, int y) {
        for (int i = x; i <= n; i += lowbit(i)) c[i] += y;
    }
    
    int query(int c[], int x) {
        int res = 0;
        for (int i = x; i; i -= lowbit(i)) res += c[i];
        return res;
    }
    
    int main() {
        cin >> n >> m;
        for (int i = 1, op, l, r; i <= m; ++i) {
            scanf("%d%d%d", &op, &l, &r);
            if (op == 1) add(c1, l, 1), add(c2, r, 1);  // l处左括号数目加一,r处右括号数目加一
            else printf("%d
    ", query(c1, r) - query(c2, l - 1));  // r的左括号数目-(l-1)的右括号数目
        }
        return 0;
    }
    

    POJ2155 Matrix
    一个n*n的只含01二维矩阵,有m个操作,每个操作两种类型:
    C x1 y1 x2 y2:把以(x1, y1)为左上角,(x2, y2)为右下角的矩阵的内的数全部反转(0变1,1变0)
    Q x y:查询a[x][y]的数是多少
    n、m~1e5

    // 翻转一个区间相当于区间的每个数加1,最后是0还是1就模2即可。
    // 加1在模2的意义下就是把01反转
    #include <bits/stdc++.h>
    
    using namespace std;
    
    typedef long long LL;
    int const N = 1e3 + 10;
    LL c[N][N], n, m, op, T;  // c[x][y]维护b[i][j]的前缀和
    
    int lowbit(int x) {
        return x & (-x);
    }
    
    void add(int x, int y, LL z) {
        for (int i = x; i <= n; i += lowbit(i)) 
            for (int j = y; j <= n; j += lowbit(j))
                c[i][j] += z;
    }
    
    LL query(int x, int y) {
        LL res = 0;
        for (int i = x; i; i -= lowbit(i))
            for (int j = y; j; j -= lowbit(j))
                res += c[i][j];
        return res;
    }
    
    int main() {
        cin >> T;
        while (T--) {
            memset(c, 0, sizeof c);
            cin >> n >> m;
            for (int i = 1, x1, y1, x2, y2; i <= m; ++i) {
                char op[2];
                scanf("%s", op);
                if (op[0] == 'C') {  // 区间增加1
                    scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
                    add(x2 + 1, y2 + 1, 1);
                    add(x1, y1, 1);
                    add(x2 + 1, y1, -1);
                    add(x1, y2 + 1, -1);
                }
                else {  // 单点查询
                    scanf("%d%d", &x1, &y1);
                    printf("%lld
    ", query(x1, y1) % 2);
                }
            }
            printf("
    ");
        }
        return 0;
    }
    

    3.3 树状数组6大基本模板+推公式变形

    ACM-ICPC 2018 徐州赛区网络预赛 H.Ryuji doesn't want to study
    一开始给定n个数,m次询问。每次询问两种类型:
    1 x y, 表示询问a[l]*len + a[l + 1] * (len - 1) + ... + a[r] * 1,len = r - l + 1。
    2 x y, 表示把a[x]赋值为y
    n ~ 1e5, m ~ 1e5
    题解:(sum_{i=l}^ra[i]*(r - i + 1) = (r+1)sum_{i=l}^ra[i] - sum_{i=l}^ri * a[i])
    因此,可以使用树状数组分别维护a[i]和i * a[i]的前缀和即可

    #include<bits/stdc++.h>
    using namespace std;
    
    typedef long long LL;
    
    int n, m;
    int const N = 1e5 + 10;
    LL c1[N], c2[N], a[N], sum1[N], sum2[N];  // c1[x]维护ai的前缀和,c2[x]维护i*ai的前缀和
    
    LL lowbit(LL x) {
        return x & -x;
    }
    
    // 单点修改
    void add(LL c[], int x, LL y) {
        for (int i = x; i <= n; i += lowbit(i)) c[i] += y;  // 不断往父节点跳
    }
    
    // 查询Sx前缀和
    LL query(int x, LL c[]) {
        LL res = 0;
        for (int i = x; i; i -= lowbit(i)) res += c[i];  // 不断往前一个兄弟节点跳
        return res;
    }
    
    int main()
    {
        cin >> n >> m;
        for (int i = 1; i <= n; ++i) {
            scanf("%lld", &a[i]);  // 读入a[i]
            sum1[i] = sum1[i - 1] + a[i];
            sum2[i] = sum2[i - 1] + i * a[i];
        }
        for (int i = 1; i <= n; ++i) {
            c1[i] = sum1[i] - sum1[i - lowbit(i)];
            c2[i] = sum2[i] - sum2[i - lowbit(i)];
        }
        while (m--) {
            LL op, x, y;
            scanf("%lld%lld%lld", &op, &x, &y);
            if (op == 1) {  // 询问
                printf("%lld
    ", (y + 1) * (query(y, c1) - query(x - 1, c1)) - (query(y, c2) - query(x - 1, c2)));
            }
            else {  // 单点修改
                add(c1, x, y - a[x]), add(c2, x, x * y - x * a[x]);
                a[x] = y;
            }
        }
        return 0;
    }
    
  • 相关阅读:
    asp.net购物车,订单以及模拟支付宝支付(四)---模拟支付宝支付
    asp.net购物车,订单以及模拟支付宝支付(三)---提交订单
    HDU 1525 Euclid's Game (博弈)
    Codeforces 828B Black Square(简单题)
    CodeForces 816B Karen and Coffee(前缀和,大量查询)
    Ubuntu 16.04 LTS安装 TeamViewer
    Ubuntu下安装codeblocks
    Ubuntu系统的安装Sublime3
    关于戴尔没有活动分区,遇到了“Windows安装程序无法将windows配置为在此计算机的硬件上运行”提示等
    电脑只能上QQ,不能上网浏览网站怎么解决
  • 原文地址:https://www.cnblogs.com/spciay/p/13073184.html
Copyright © 2020-2023  润新知