首页 > 其他分享 >CDQ分治

CDQ分治

时间:2024-08-10 14:27:14浏览次数:7  
标签:le int 分治 mid CDQ 数组

CDQ 分治的思想最早由 IOI2008 金牌得主陈丹琦在高中时整理并总结,它也因此得名。

CDQ分治的思想常用于离线解决点对之间的问题,最经典的如三维偏序。

解决这类问题的过程为:

  • 将序列 $ (l, r) $ 分为。

  • 递归处理序列$ (l, mid) $ 和 $ (mid + 1, r) $中的答案。

  • 设计算法处理对于点对

$ \color {skyblue}P3810 \text【模板】三维偏序(陌上花开)$

题意

有 $ n $ 个元素,第 $ i $ 个元素有 $ a_i,b_i,c_i $ 三个属性,设 $ f(i) $ 表示满足 $ a_j \leq a_i $ 且 $ b_j \leq b_i $ 且 $ c_j \leq c_i $ 且 $ j \ne i $ 的 \(j\) 的数量。

对于 $ d \in [0, n) $,求 $ f(i) = d $ 的数量。

思路

考虑先将数组按 $ a $ 排序,然后开始分治。

假设当前在处理区间 \(l, r\) ,并且已经统计完的答案,那么就要思考如何处理点对 $ (i, j) (l \le i \le mid), (mid + 1 \le j \le r) $ 间的答案。

首先由于开始时已将数组按 $ a $ 排序,所以左区间内的所有点的 $ a $ 都小于右区间。

那么对于 $ j \in [mid + 1, r] $ , $ f(j) $ 应加上 $ b_i \le b_j $ && $ c_i \le c_j $ $ (i \in [l, mid]) $ 的数量。

为了处理第二维 $ b $ ,我们可以将区间 $ (l, mid) $ 和 $ (mid + 1, r) $ 都按 $ b $ 排序,然后使用双指针维护 $ b_i \le b_j $。

维护第三维 $ c $ , 我们可以使用树状数组。

在 $ i $ 指针向右移的过程中,我们把树状数组中 $ c_i $ 位上 $ +1 $,这样更新 $ f(j) $ 时只需在树状数组中查询 $ \le c_j $ 的个数即可。

注意:进入递归前需要对原数组进行去重,否则会出错。

代码

#include<bits/stdc++.h>
#define int long long
#define Debug puts("Oops!")
using namespace std;

const int N = 2e5 + 5, M = 5e5 + 5;

int n, m, k;
int res[N];

struct sb {
	int x, y, z;
	int cnt, f;
}p[N], a[N]; 

struct BIT {
    int t[N];
    int lowbit(int x) {return x & -x;}
    void add(int x, int c) {for(; x <= k; x += lowbit(x)) t[x] += c;}
    int ask(int x) {int res = 0;for(; x; x -= lowbit(x)) res += t[x]; return res;}
}T;

bool cmp1(sb x, sb y) {
	if(x.x != y.x) return x.x < y.x;
	if(x.y != y.y) return x.y < y.y;
	return x.z < y.z;
}

bool cmp2(sb x, sb y) {
	if(x.y != y.y) return x.y < y.y;
	return x.z < y.z;
}

void cdq(int l, int r) {
    if(l == r) return ;
    int mid = l + r >> 1;
    cdq(l, mid), cdq(mid + 1, r);
	sort(a + l, a + mid + 1, cmp2), sort(a + mid + 1, a + r + 1, cmp2);
	int i = l, j = mid + 1;
	while(j <= r) {
		while(i <= mid && a[i].y <= a[j].y) T.add(a[i].z, a[i].cnt), i++;
		a[j].f += T.ask(a[j].z);
		j++;
	}
	for(int k = l; k < i; k++) T.add(a[k].z, -a[k].cnt);
}

inline int read() {
	int x = 0, f = 1; char c = getchar();
	while(!isdigit(c)) {if(c == '-') f = -1; c = getchar();}
	while(isdigit(c)) x = x * 10 + c - '0', c = getchar();
	return x * f;
}

signed main() {
//	freopen(".in", "r", stdin);
//	freopen(".out", "w", stdout);
	n = read(), k = read();
	for(int i = 1; i <= n; i++)	p[i].x = read(), p[i].y = read(), p[i].z = read();
	sort(p + 1, p + 1 + n, cmp1);
	int cnt = 0;
	for(int i = 1; i <= n; i++) {
		cnt++;
		if(p[i].x != p[i + 1].x || p[i].y != p[i + 1].y || p[i].z != p[i + 1].z)
			a[++m].x = p[i].x, a[m].y = p[i].y, a[m].z = p[i].z, a[m].cnt = cnt, cnt = 0;
	}
    cdq(1, m);
	for(int i = 1; i <= m; i++) res[a[i].f + a[i].cnt] += a[i].cnt;
	for(int i = 1; i <= n; i++) cout << res[i] << endl;
	return 0;
}

标签:le,int,分治,mid,CDQ,数组
From: https://www.cnblogs.com/zeta-y/p/18352255

相关文章

  • cdq分治总结
    \(cdq\)分治是一种离线分治算法,可以将动态问题改变为静态问题,不适用于强制在线。其实现时通常将需要进行的操作存进一个结构体,然后对这些操作进行分治。打\(cdq\)分治时一个直观的感受就是很好想思路,但就是不知道怎么打。。。它一共有三个需要干的1找到范围中点\(mid\)......
  • 【CDQ分治】三元环
    三元环HDU-7439思路考虑\(3\)个点的有向图,要么成环,要么有一个点入度为\(2\),假设第个点的入度为\(d_i\),答案为\(C_n^3-\sum\limits_{i=1}^nC_{d_i}^2\)。根据题目关系,\(i\rightarrowj\)当且仅当\(i<j\and\f_i<f_j\and\g_i<g_j\),否则就是\(j\rightarrowi......
  • 【CDQ分治】【模板】三维偏序(陌上花开)
    P3810【模板】三维偏序(陌上花开)-洛谷|计算机科学教育新生态(luogu.com.cn)#include<bits/stdc++.h>usingnamespacestd;usingi64=longlong;template<typenameT>structBIT{#ifndeflowbit#definelowbit(x)(x&(-x));#endifintn;vector<T&......
  • 【CDQ分治】[P5094 [USACO04OPEN] MooFest G 加强版
    P5094[USACO04OPEN]MooFestG加强版-洛谷|计算机科学教育新生态(luogu.com.cn)#include<bits/stdc++.h>usingnamespacestd;usingi64=longlong;intmain(){ios::sync_with_stdio(false);cin.tie(nullptr);intn;cin>>n;vecto......
  • [算法] 一些分治
    普通分治其实没啥,每次只计算跨越分治中心的区间的贡献,剩下的递归到左右两边进行分治;时间复杂度:分治树高度为$\Theta(\logn)$,乘上其他操作的复杂度即可;例题一:现在有一个$n$阶排列$a$,计算:\[\sum^{n}_{i=1}\sum^{n}_{j=i}\min(a_i,a_{i+1},...,a_j)\]......
  • 20240806:点分治,虚树选做
    POJ-1741Tree题意:给定一棵树,求多少无序对\((u,v)\)满足\(\text{dist}(u,v)\lek\)。对于树上的路径进行分类:经过根;不经过根。第二类路径点可以通过递归子树求出。对于经过根的路径,可以一遍dfs求出每个点到根的距离\(\text{dis}(u)\)。问题转化为求\(\text{dis}(u)......
  • CDQ分治
    CDQ分治CDQ分治是一种思想而不是具体的算法,与动态规划类似。目前这个思想的拓展十分广泛,依原理与写法的不同,大致分为三类:解决和点对有关的问题。1D动态规划的优化与转移。通过CDQ分治,将一些动态问题转化为静态问题。解决和点对有关的问题这类问题多数类似于「给定一......
  • 根号分治(待补充)
    最近有根号分治的题都没那么熟悉,想到了方向感也很差,故写一点题解。LCA查询看到题目中的条件\(\sumk\le10^5\)说明\(k\leS\)的个数很多,\(S\lek\)的个数很少。那么对于前者,考虑一开始最朴素的\(O(S^2)\)的暴力,枚举集合中的两个点,求\(LCA\)总时间复杂度\(O(\fra......
  • 「Day 2—贪心问题&分治&前缀和」
    贪心问题定义顾名思义,越贪越好。。。习题P1094[NOIP2007普及组]纪念品分组思路简单来说:最少的+最多的,利用双指针。代码#include<algorithm>#include<iostream>usingnamespacestd;intw,n;intp[30005];intmain(){cin>>w;cin>>n;for(inti=1;i......
  • 边分治维护强连通分量(CF1989F,P5163)
    这里的边分治和树上的点分治边分治不一样,是维护强连通分量用的,每条边有一个出现时间,通过将每条边按连通关系分流重新排列,从而维护每个时间点整张图的连通性。具体的,这个算法是维护这样的一类问题:n个点,m条边按时间顺序依次加入,每加入一条边,你需要回答一些问题,比如在这个时间点......