Loading

CDQ 分治

写在前面

学完分块,跑来学 CDQ 分治,导致 qbxt 导学的题没做完,然后就,挨老吕批了 qwq

可恶的 ycc 和 myz(偷偷往题单里放题 = =)

本文主要借 (chao) 鉴 (xi) 自 CDQ 分治也有自己的想法

如果有什么不对的地方,可以直接看原文

引子

关于 CDQ 分治

它不是一种算法,而是一种思想,在 \(OI\) 中可以把它分为三类

  • cdq 分治解决和点对有关的问题
  • cdq 分治优化 1D/1D 动态规划的转移
  • 通过 cdq 分治,将一些动态问题转化为静态问题

CDQ 分治解决和点对有关的问题

给你一个长度为 \(n\) 的序列,统计有一些特性的点对有多少个,又或者说是找到一对点使得一些函数的值最大之类的

基本流程

1.找序列的中点

2.把所有的点对 \((i,j)\) 分为三类

第一种:\(1\leq i \leq mid,~~1 \leq j \leq mid\)

第二种:\(1\leq i \leq mid,~~mid + 1 \leq j\leq n\)

第三种: \(mid + 1\leq i \leq n,~~mid + 1\leq j \leq n\)

3.把 \((1,n)\) 这个序列拆成两部分,对于第一种和第三种可以直接用递归方式求解

4.处理第二种情况

三维偏序

给定一个序列,每个点有两个属性 \((a,b)\),试求:这个序列里有多少对点对 \((i, j)\) 满足 \(i < j,a_i < a_j, b_i < b_j\)

统计点对的个数,二话不说,直接上 CDQ

假设当前解决要解决的区间为 \([l,r]\)

根据上面所说 \([l, mid], [mid + 1, r]\) 我们已经都通过递归求出来了,现在要解决的就是上面的第二种情况:

也就是统计满足 \(l \leq i \leq mid\)\(mid + 1 \leq j \leq r\) 的点对 \((i,j)\) 有多少个还满足上面的条件

很显然第一个条件它一定满足,考虑剩下那俩条件 \(a_i < a_j\)\(b_i < b_j\)

首先按照 \(a_i\) 的值对区间 \([i, mid]\)\([mid + 1,~j]\) 从小到大排个序,然后枚举 \(j\) ,把所有的 \(a_i < a_j\)\(i\) 点的 \(b\) 属性插入到树状数组中(如果 \(i\) 点的 b 属性为 x 那么 x 这个点就 + 1),那么求小于 \(b_j\) 的点有多少个相当于就是求个前缀和了

因为 \(a\) 是排好序的,所以可以用一个双指针把它们插入树状数组中去,复杂度为 \(O(n)\)

这个操作总的时间复杂度为 \(O(n log~n)\)

总的复杂度: \(O(n~log^2n)\)

板子题

三维偏序(陌上花开)

/*
work by: Ariel
*/
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 200005;
int read() {
   int x = 0, f = 1; char c = getchar();
   while(c < '0' || c > '9') {if(c == '-') f = -1;c = getchar();}
   while(c >= '0' && c <= '9') {x = x * 10 + c - '0';c = getchar();}
   return x * f;
}
struct node{int a, b, c, cnt, ans;}s1[N], s2[N];
int n, m, k, tree[N << 1], tot, cnt, Ans[N]; 
bool cmp1(node x, node y) {
  if (x.a == y.a) {
  	 if (x.b == y.b) return x.c < y.c;
  	 else return x.b < y.b;
  }
  return x.a < y.a;
}
bool cmp2(node x, node y) {
  if (x.b == y.b) return x.c < y.c;
  return x.b < y.b;
}
void update(int x, int y) {
   for (int i = x; i <= k; i += i & (-i)) tree[i] += y;
}
int query(int x) {
   int ans = 0;
   for (int i = x; i; i -= i & (-i)) ans += tree[i];
   return ans;
}
void CDQ(int l, int r) {
   if (l == r) return ;
   int mid = (l + r) >> 1;
   CDQ(l, mid), CDQ(mid + 1, r);
   sort(s2 + l, s2 + mid + 1, cmp2), sort(s2 + mid + 1, s2 + r + 1, cmp2);
   int i = l;
   for (int j = mid + 1; j <= r; j++) {
   	   while (s2[j].b >= s2[i].b && i <= mid) {
   	   	     update(s2[i].c, s2[i].cnt);
   	   	     i++;
		}
		s2[j].ans += query(s2[j].c); 
   }
   for (int o = l; o < i; o++) update(s2[o].c, -s2[o].cnt);//树状数组清空 
}
int main() { 
   n = read(), k = read();
   for (int i = 1, a, b, c; i <= n; i++) {
   	a = read(), b = read(), c = read();
   	s1[i].a = a, s1[i].b = b, s1[i].c = c;
   }
   sort(s1 + 1, s1 + n + 1, cmp1);
   for (int i = 1; i <= n; i++) {
   	   tot++;
   	   if (s1[i].a != s1[i + 1].a || s1[i].b != s1[i + 1].b || s1[i].c != s1[i + 1].c) {
   	   	   s2[++m].a = s1[i].a, s2[m].b = s1[i].b, s2[m].c = s1[i].c, s2[m].cnt = tot;
   	   	   tot = 0;
		}
   }
   CDQ(1, m);
   for (int i = 1; i <= m; i++) Ans[s2[i].ans + s2[i].cnt - 1] += s2[i].cnt;
   for (int i = 0; i < n; i++) printf("%d\n", Ans[i]); 
}
posted @ 2021-07-12 21:10  Dita  阅读(85)  评论(0)    收藏  举报