• cdq分治 & 整体二分 小记(咕咕咕)


    只保证自己看懂。
    cdq分治和整体二分是两个比较重要的离线算法。


    cdq分治

    1.三维偏序

    (f(i)) 表示满足 (a_j < a_i,b_j<b_i,c_j<c_i)(j) 的数量。
    考虑只有一维的时候,就是排序。
    考虑只有二维的时候,就是按 (a_j < a_i) 排序之后,从 (1)(n) 枚举 (i) ,把 (b_i) 加入树状数组,则 (f(i)) 为满足树状数组中满足 (b_j < b_i) 的数目。
    考虑三维的时候,先按 (a_j < a_i) 排序,然后按 (b_j < b_i) 做归并排序(要保证 (a_j < a_i) 不变,归并排序是稳定的,所以可以保证)。
    对于区间 ([l,r]) ,此时 ([l,mid])((mid,r]) 已经排好序并贡献答案了,对于 (j in [l,mid],i in (mid,r]) 总有 (b_j < b_i) ,所以我们在 merge 的时候套上一个树状数组,算出 (c_j < c_i) 的数目。
    总复杂度为 (O(n log n log ext{值域大小})) ,实际上离散化之后就是 (O(n log^2 n))
    例题:Luogu P3810
    注意这题为“ (leq) ” ,所以我们要先去重再做,具体细节看下面代码。

    #include <cstdio>
    #include <cctype>
    #include <algorithm>
    #define MAX_N (100000 + 5)
    #define MAX_K (200000 + 5)
    using std::sort;
    
    int n, K;
    struct Node {
    	int a, b, c;
    	int cnt;
    	int sum;
    	bool operator < (const Node &x) const {
    		if (a != x.a) return a < x.a;
    		if (b != x.b) return b < x.b;
    		return c < x.c;
    	}
    	bool operator != (const Node &x) const {
    		return a != x.a || b != x.b || c != x.c;
    	}
    } a[MAX_N], b[MAX_N];
    int s[MAX_K];
    int ans[MAX_N];
    
    int Read() {
    	int res = 0;
    	char ch = getchar();
    	while (!isdigit(ch)) ch = getchar();
    	while (isdigit(ch)) res = res * 10 + ch - '0', ch = getchar();
    	return res;
    }
    
    int lowbit(int x) {
    	return x & -x;
    }
    
    void Add(int x, int val) {
    	for (; x <= K; x += lowbit(x))
    		s[x] += val;
    }
    
    int Sum(int x) {
    	int res = 0;
    	for (; x; x -= lowbit(x))
    		res += s[x];
    	return res;
    }
    
    void cdq(int l, int r) {
    	if (l == r) return;
    	int mid = l + r >> 1;
    	cdq(l, mid);
    	cdq(mid + 1, r);
    	int i = l, j = mid + 1, k = 0;
    	while (i <= mid && j <= r) {
    		if (a[i].b <= a[j].b) {
    			Add(a[i].c, a[i].cnt);
    			b[++k] = a[i++];
    		} else {
    			a[j].sum += Sum(a[j].c);
    			b[++k] = a[j++];
    		}
    	}
    	while (i <= mid) {
    		Add(a[i].c, a[i].cnt);
    		b[++k] = a[i++];
    	}
    	while (j <= r) {
    		a[j].sum += Sum(a[j].c);
    		b[++k] = a[j++];
    	}
    	for (int i = l; i <= mid; ++i)
    		Add(a[i].c, -a[i].cnt);
    	for (int i = 1; i <= k; ++i)
    		a[l + i - 1] = b[i];
    }
    
    int main() {
    	n = Read(), K = Read();
    	for (int i = 1; i <= n; ++i) {
    		a[i].a = Read(), a[i].b = Read(), a[i].c = Read();
    		a[i].cnt = 1;
    	}
    	sort(a + 1, a + n + 1);
    	int m = 0;
    	for (int i = 1; i <= n; ++i) {
    		if (a[i] != a[i + 1]) a[++m] = a[i];
    		else a[i + 1].cnt += a[i].cnt;
    	}
    	cdq(1, m);
    	for (int i = 1; i <= m; ++i)
    		ans[a[i].sum + a[i].cnt - 1] += a[i].cnt;
    	for (int i = 0; i < n; ++i)
    		printf("%d
    ", ans[i]);
    	return 0;
    }
    

    2.应用

    例题:Luogu P3157
    (j < i,a_j > a_i) 再加上时间,那就是三维偏序,直接套cdq分治就好了。


    整体二分

    1.静态区间第 (k)

    整体二分是什么?
    显然是二分啊。
    我们先把输入 (a_i) 和询问看作两种操作,一起放入 (q_1,q_2,q_3,cdots,q_{n+m})
    于是我们设当前二分的答案区间为 ([l,r]) ,令 (mid) 为其中值。
    又设二分的操作序号区间为 ([L,R])
    我们先将 ([L,R]) 中的满足 (a_i in [l,mid])(i) 加入一个数组 (t_i)中,若 (a_i) 满足条件则第 (i)(+1) ,即 (t_i gets t_i+1)
    然后对于 ([L,R]) 中的每一个询问操作,设询问区间为 ([ql,qr]) ,则设 (res = sumlimits_{i=ql}^{qr}t_i) (即满足 (a_i in [ql,qr]) 的数量)。
    (k leq res) ,则答案一定在 ([l,mid]) 中;若 (k > res) ,则答案一定在 ((mid,r]) 中。
    我们在分别处理 ([l,mid])((mid,r]) 即可,注意要同时更新 ([L,R])
    至于 (t_i) 其实可以用树状数组维护。
    则二分的复杂度为 (O(nlog ext{值域大小})) ,树状数组修改和查询的复杂度为 (O(log n)) ,总复杂度为 (O(n log n log ext{值域大小}))
    当然,离散化之后可以变成 (O(n log^2 n)) ,这里就不写了。
    例题:Luogu P3834
    具体细节看代码。
    注意 mid = l + r >> 1 是向下取整, mid = (l + r) / 2 是向 (0) 取整。

    #include <cstdio>
    #include <cctype>
    #define MAX_N (200000 + 5)
    #define MAX_M (200000 + 5)
    
    int n, m;
    struct Opt {
    	int l, r, k, idx, opt;
    } q[MAX_N + MAX_M], q1[MAX_N + MAX_M], q2[MAX_N + MAX_M];
    int s[MAX_N];
    int ans[MAX_M];
    
    int Read() {
    	int res = 0, sign = 0;
    	char ch = getchar();
    	while (!isdigit(ch)) sign |= ch == '-', ch = getchar();
    	while (isdigit(ch)) res = res * 10 + ch - '0', ch = getchar();
    	return sign ? -res : res;
    }
    
    int lowbit(int x) {
    	return x & -x;
    }
    
    void Add(int pos, int val) {
    	for (; pos <= n; pos += lowbit(pos))
    		s[pos] += val;
    }
    
    int Sum(int pos) {
    	int res = 0;
    	for (; pos; pos -= lowbit(pos))
    		res += s[pos];
    	return res;
    }
    
    void Solve(int l, int r, int L, int R) {
    	if (L > R) return;
    	if (l == r) {
    		for (int i = L; i <= R; ++i)
    			if (q[i].opt) ans[q[i].idx] = l;
    		return;
    	}
    	int mid = l + r >> 1, cnt1 = 0, cnt2 = 0, res;
    	for (int i = L; i <= R; ++i) {
    		if (q[i].opt) {
    			res = Sum(q[i].r) - Sum(q[i].l - 1);
    			if (res >= q[i].k) q1[++cnt1] = q[i];
    			else q[i].k -= res, q2[++cnt2] = q[i];
    		} else {
    			if (q[i].k <= mid) q1[++cnt1] = q[i], Add(q[i].idx, 1);
    			else q2[++cnt2] = q[i];
    		}
    	}
    	for (int i = 1; i <= cnt1; ++i) 
    		q[L + i - 1] = q1[i];
    	for (int i = 1; i <= cnt2; ++i)
    		q[L + cnt1 + i - 1] = q2[i];
    	for (int i = 1; i <= cnt1; ++i)
    		if (!q1[i].opt) Add(q1[i].idx, -1);
    	Solve(l, mid, L, L + cnt1 - 1);
    	Solve(mid + 1, r, L + cnt1, R);
    }
    
    int main() {
    	n = Read(), m = Read();
    	for (int i = 1; i <= n; ++i) 
    		q[i] = (Opt){0, 0, Read(), i, 0};
    	for (int i = 1; i <= m; ++i)
    		q[i + n] = (Opt){Read(), Read(), Read(), i, 1};
    	Solve(-1e9, 1e9, 1, n + m);
    	for (int i = 1; i <= m; ++i)
    		printf("%d
    ", ans[i]);
    	return 0;
    }
    

    2.动态区间第 (k)

    其实把输入 (a_i) 也看作是操作 ( exttt{C} i a_i)
    于是可以把输入操作看作 (operatorname{add}(i,1)) ,把修改操作拆成 (operatorname{add}(x,-1))(operatorname{add}(x,1))
    记录每一次 (operatorname{add}) 操作时(a_x) 即可。
    例题:Luogu P2617

    #include <cstdio>
    #include <cctype>
    #include <cstring>
    #define MAX_N (100000 + 5)
    #define MAX_M (100000 + 5)
    
    int n, m;
    int a[MAX_N];
    struct Opt {
    	char opt;
    	int l, r, k, idx;
    } q[MAX_N + MAX_M * 2], q1[MAX_N + MAX_M * 2], q2[MAX_N + MAX_M * 2];
    int s[MAX_N];
    int ans[MAX_N];
    
    int Read() {
    	int res = 0;
    	char ch = getchar();
    	while (!isdigit(ch)) ch = getchar();
    	while (isdigit(ch)) res = res * 10 + ch - '0', ch = getchar();
    	return res;
    }
    
    int lowbit(int x) {
    	return x & -x;
    }
    
    void Add(int pos, int val) {
    	for (; pos <= n; pos += lowbit(pos))
    		s[pos] += val;
    }
    
    int Sum(int pos) {
    	int res = 0;
    	for (; pos; pos -= lowbit(pos))
    		res += s[pos];
    	return res;
    }
    
    void Solve(int l, int r, int L, int R) {
    	if (L > R) return;
    	if (l == r) {
    		for (int i = L; i <= R; ++i)
    			if (q[i].opt == 'Q') ans[q[i].idx] = l;
    		return;
    	}
    	int mid = l + r >> 1, cnt1 = 0, cnt2 = 0, res;
    	for (int i = L; i <= R; ++i) {
    		if (q[i].opt == 'C') {
    			if (q[i].k <= mid) q1[++cnt1] = q[i], Add(q[i].l, q[i].r);
    			else q2[++cnt2] = q[i];
    		} else {
    			res = Sum(q[i].r) - Sum(q[i].l - 1);
    			if (q[i].k <= res) q1[++cnt1] = q[i];
    			else q[i].k -= res, q2[++cnt2] = q[i];
    		}
    	}
    	for (int i = 1; i <= cnt1; ++i)
    		q[L + i - 1] = q1[i];
    	for (int i = 1; i <= cnt2; ++i)
    		q[L + cnt1 + i - 1] = q2[i];
    	for (int i = 1; i <= cnt1; ++i)
    		if (q1[i].opt == 'C') Add(q1[i].l, -q1[i].r);
    	Solve(l, mid, L, L + cnt1 - 1);
    	Solve(mid + 1, r, L + cnt1, R);
    }
    
    int main() {
    	n = Read(), m = Read();
    	int cnt = 0;
    	for (int i = 1; i <= n; ++i){
    		a[i] = Read();
    		q[++cnt] = (Opt){'C', i, 1, a[i], 0};
    	}
    	char ch;
    	int x, y;
    	for (int i = 1; i <= m; ++i) {
    		ch = getchar();
    		while (!isalpha(ch)) ch = getchar(); 
    		x = Read(), y = Read();
    		if (ch == 'C') {
    			q[++cnt] = (Opt){'C', x, -1, a[x], 0};
    			a[x] = y;
    			q[++cnt] = (Opt){'C', x, 1, y, 0};
    		} else 
    			q[++cnt] = (Opt){'Q', x, y, Read(), i};
    	}
    	memset(ans, -1, sizeof ans);
    	Solve(0, 1e9, 1, cnt);
    	for (int i = 1; i <= m; ++i)
    		if (~ans[i]) printf("%d
    ", ans[i]);
    	return 0;
    }
    
  • 相关阅读:
    20172318 2017-2018-2 《程序设计与数据结构》第5周学习总结
    20172318 2017-2018-2 《程序设计与数据结构》第4周学习总结
    20172318 2017-2018-2 《程序设计与数据结构》实验1报告
    20172318 《程序设计与数据结构》第三周学习总结
    20172318 2017-2018-2 《程序设计与数据结构》第2周学习总结
    20172318 2016-2017-2 《Java程序设计》第一周学习总结
    寒假作业03
    寒假作业02
    20172311 2017-2018-2 《程序设计与数据结构》第五周学习总结
    20172311 《程序设计与数据结构》第四周学习总结
  • 原文地址:https://www.cnblogs.com/kcn999/p/13770946.html
Copyright © 2020-2023  润新知