• BZOJ 2683: 简单题(CDQ分治 + 树状数组)


    题意

    你有一个(N*N)的棋盘,每个格子内有一个整数,初始时的时候全部为(0),现在需要维护两种操作:

    命令 参数限制 内容
    (1 x y A) (1le x,y le N),A是正整数 将格子(x,y)里的数字加上(A)
    (2 x1 y1 x2 y2) (1le x1le x2le N,1le y1le y2le N) 输出(x1 y1 x2 y2)这个矩形内的数字和
    (3) 终止程序

    (1<=N<=500000),操作数不超过(200000)个,内存限制(20M)

    题解

    这个题是 cdq分治 的裸题吧。

    一维:时间(按输入顺序就行了)

    二维:(x)坐标(cdq分治)

    三维:(y)坐标(树状数组)

    这个题比较裸,但是cdq分治细节还是有一点的(调的错误我可以列一版了。。)

    算法讲解

    但我想简单讲一下cdq分治(因为网上很多都很坑没讲清楚)

    cdq是专门解决多维偏序的问题,比如像这一道题统计二维矩形的权值,或者直接求高维偏序的个数。

    如果不用cdq分治,就只能树套树或者KD-tree这种巨型工业数据结构。而且树套树常常空间和常数都很恐怖,并且很难写……

    cdq分治是个比较好写的东西,但其中的思想十分的巧妙和神奇。

    你应该学过归并排序求逆序对吧,那是最裸的cdq了。他就是利用了左边的答案来更新右边的答案,cdq就是在这个方面不同于普通的分治。

    它每次算答案,只能在右边区间算也就是([mid+1,r])。这是为什么可以这样呢,因为你初始给它的序列,按这样算的话,绝对只会算它原序列左边的贡献,不会算到右边去。(想一想,为什么) 这个只需要自己模拟下分治的区间划分和左右区间考虑就行了。

    这就可以会强制使你一开始的那一维有序,对答案计算是正确的。(但切记最后给你的序列不一定是按你给它的顺序了!!!)

    然后它中间会有一个排序比较的过程,这就可以使第二个维度变得有序了。(最后的序列一定是第二维度有序的) 然后根据前两个维度算答案就行了,后面的维度全都是附加在这两个维度上面的。

    总的步骤:

    1. 分开(递归计算左区间和右区间)
    2. 计算(用左区间来统计,右区间来加上贡献)
    3. 合并(将当前序列变得有序)

    又回到题解

    这道题,就是对于所有操作进行cdq分治(一般都是对于操作进行分治)。

    第三维用树状数组统计(y)的前缀和就行了,因为(x)已经排好序了,所以可以直接算了。

    左区间只执行Add操作,右区间只执行Sum操作。

    对于一个询问操作,要将它拆成4个询问操作(就是类似询问二维前缀和),加加减减就行了。

    注意几个细节(我调了很久的点)

    1. 树状数组清空的时候,下标不是val而是y
    2. 拆矩阵的时候,一定要不要写错下标;

    然后多拍几组,写个暴力很容易查出来的。

    代码

    #include <bits/stdc++.h>
    #define For(i, l, r) for(register int i = (l), _end_ = (int)(r); i <= _end_; ++i)
    #define Fordown(i, r, l) for(register int i = (r), _end_ = (int)(l); i >= _end_; --i)
    #define Set(a, v) memset(a, v, sizeof(a))
    using namespace std;
    
    inline bool chkmin(int &a, int b) {return b < a ? a = b, 1 : 0;}
    inline bool chkmax(int &a, int b) {return b > a ? a = b, 1 : 0;}
    
    inline int read() {
        int x = 0, fh = 1; char ch = getchar();
        for (; !isdigit(ch); ch = getchar() ) if (ch == '-') fh = -1;
        for (; isdigit(ch); ch = getchar() ) x = (x<<1) + (x<<3) + (ch ^ '0');
        return x * fh;
    }
    
    void File() {
    #ifdef zjp_shadow
    	freopen ("P2683.in", "r", stdin);
    	freopen ("P2683.out", "w", stdout);
    #endif
    }
    
    int n;
    
    const int N = 800100;
    struct Opt {
    	int x, y, type, id, val;
    	inline bool operator < (const Opt &rhs) const {
    		return (x ^ rhs.x) ? x < rhs.x : type < rhs.type;
    	}
    };
    Opt lt[N], tmp[N];
    
    #define lowbit(x) (x & -(x))
    struct Fenwick_Tree {
    	int c[500100];
    	inline void Add(int pos, int val) { for (; pos <= n; pos += lowbit(pos) ) c[pos] += val; }
    	inline int Sum(int pos) { int res = 0; for (; pos; pos -= lowbit(pos) ) res += c[pos]; return res; }
    	inline void Clear(int pos) { for (; pos <= n; pos += lowbit(pos) ) if (c[pos]) c[pos] = 0; else break; }
    };
    Fenwick_Tree T;
    
    int ans[N];
    
    void Cdq(int l, int r) {
    	if (l == r) return ;
    	int mid = (l + r) >> 1;
    	Cdq(l, mid); Cdq(mid + 1, r);
    	int lp = l, rp = mid + 1, o = l;
    	while (lp <= mid && rp <= r) {
    		if (lt[lp] < lt[rp]) {
    			if (lt[lp].type == 1) T.Add(lt[lp].y, lt[lp].val);
    			tmp[o ++] = lt[lp ++];
    		} else {
    			if (lt[rp].type == 2) ans[lt[rp].id] += lt[rp].val * T.Sum(lt[rp].y);
    			tmp[o ++] = lt[rp ++];
    		}
    	}
    
    	while (lp <= mid) tmp[o ++] = lt[lp ++];
    	while (rp <= r) {
    		if (lt[rp].type == 2) ans[lt[rp].id] += lt[rp].val * T.Sum(lt[rp].y);
    		tmp[o ++] = lt[rp ++];
    	}
    
    	For (i, l, mid) if (lt[i].type == 1) T.Clear(lt[i].y);
    	For (i, l, r) lt[i] = tmp[i];
    }
    
    int qcnt, acnt;
    inline void Addq(int x, int y, int type, int id, int val) {
    	lt[++qcnt] = (Opt){x, y, type, id, val};
    }
    
    int main () {
    	File() ;
    	n = read();
    	for (;;) {
    		int opt = read();
    		if (opt == 3) break ;
    		int xa, ya, xb, yb, val;
    		if (opt == 1) {
    			xa = read(); ya = read(); val = read();
    			Addq(xa, ya, 1, 0, val);
    		} else {
    			xa = read(); ya = read();
    			xb = read(); yb = read();
    			Addq(xa - 1, ya - 1, 2, (++ acnt), 1);
    			Addq(xa - 1, yb, 2, acnt, -1);
    			Addq(xb, ya - 1, 2, acnt, -1);
    			Addq(xb, yb, 2, acnt, 1);
    		}
    	}
    	Cdq(1, qcnt);
    	For (i, 1, acnt) printf ("%d
    ", ans[i]);
        return 0;
    }
    

  • 相关阅读:
    [转]一键安装藏隐患,phpStudy批量入侵的分析与溯源
    Vue Cli安装以及使用
    全局安装 Vue cli3 和 继续使用 Vue-cli2.x
    [转]局域网共享一键修复 18.5.8 https://zhuanlan.zhihu.com/p/24178142
    DELPHI中千万别直接使用CreateThread ,建议使用BeginThread(在C++中无大问题,可是到了DELPHI中情况就不一样了)
    [转]【Delphi】 Thread.Queue与Synchronize的区别
    如何使用Windows Library文件进行持久化
    chromium中的性能优化工具syzyProf
    [转]室友靠打游戏拿30万offer,秘密竟然是……
    .NET中的三种Timer的区别和用法
  • 原文地址:https://www.cnblogs.com/zjp-shadow/p/8447545.html
Copyright © 2020-2023  润新知