CDQ分治笔记+例题
CDQ分治是一种离线分治算法,它基于时间顺序对操作序列进行分治。
看这样一个问题: 在一个三维坐标系中,有若干个点,每个点都有对应的坐标 \((X_i , Y_i , Z_i)\) ,我们要对于每个点求所有满足 \(X_j <=X_i, Y_j <= Y_i , Z_j <= Z_i\) 的 j 的数量。
考虑在二维平面上怎么做:我们可以将所有点按照 X 坐标排序,再用一个树状数组或者其他数据结构维护 Y 坐标,每次读到一个点就统计答案,然后将这个点加入树状数组。这样可以同时保证 X,Y 坐标都满足条件。
如果这时再多一维,我们就没办法再用这种方法排序了,按照一维排序必然会打乱另外两维。这时我们考虑CDQ分治,先按 X 坐标排序,然后对序列进行分治,每次分出左右两段子序列,这时我们可以发现,左序列中每个点的 X 坐标小于右序列中点的 X 坐标,这时分别将左右序列按照 Y 坐标排序。统计答案时,只更新右序列中点的答案,只在树状数组中加入左序列的点。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
const int N = 100005;
const int M = 200005;
int C[M], n, maxk, nn, ans[N];
struct node {
	int a, b, c, tot, num;
} e[N];
int lowbit(int x) {
	return x & -x;
}
void add(int x, int k) {
	for(; x <= maxk; x += lowbit(x)) C[x] += k;
}
int query(int x) {
	int answ = 0;
	for(; x; x -= lowbit(x)) answ += C[x];
	return answ;
}
bool cmp1(node x, node y) {
	if(x.a == y.a && x.b == y.b) return x.c < y.c;
	if(x.a == y.a) return x.b < y.b;
	return x.a < y.a;
}
void cdq(int l, int r) {
	int mid = (l + r) >> 1;
	if(l != mid) cdq(l, mid);
	if(mid + 1 != r) cdq(mid + 1, r);
	node p[N];
	int pt = 0, i = l, j = mid + 1;
	while(i <= mid && j <= r) {//对于第二维归并排序
		if(e[i].b <= e[j].b) add(e[i].c, e[i].tot), p[++pt] = e[i++];//插入
		else e[j].num += query(e[j].c), p[++pt] = e[j++];//更新答案
	}
	while(i <= mid) add(e[i].c, e[i].tot), p[++pt] = e[i++];
	while(j <= r) e[j].num += query(e[j].c), p[++pt] = e[j++];
	for(int t = l; t <= mid; t++) add(e[t].c, -e[t].tot);//树状数组清空
	for(int t = 1, u = l; t <= pt && u <= r; t++, u++) e[u] = p[t];//更新序列
}
int main() {
//	freopen("data.in", "r", stdin);
	scanf("%d%d", &nn, &maxk);
	for(int i = 1, a, b, c; i <= nn; i++) {
		scanf("%d%d%d", &a, &b, &c);
		e[i].a = a, e[i].b = b, e[i].c = c;
		e[i].num = 0; e[i].tot = 1;
	}
	sort(e + 1, e + 1 + nn, cmp1);//先按照第一维排序
	n = 1;
	for(int i = 2; i <= nn; i++) {
		if(e[i].a == e[n].a && e[i].b == e[n].b && e[i].c == e[n].c) e[n].tot++;
		else e[++n] = e[i];
	}
	cdq(1, n);
	for(int i = 1; i <= n; i++) {
		ans[e[i].num + e[i].tot - 1] += e[i].tot;
	}
	for(int i = 0; i < nn; i++) printf("%d\n", ans[i]);
	return 0;
}
例题1 : P3157 [CQOI2011]动态逆序对
考虑如和将其转化为三维偏序问题,我们要计算删除一个点对答案对贡献,即删除的这个点左边比它大的点的数量和右边比他小的点的数量,这样三维分别是删除顺序,序列中位置,元素大小。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
typedef long long lld;
const int N = 100005;
const int M = 50005;
int n, m, pos[N], ansper[N], ansnxt[N];
struct node {
   int x, y, type;
} e[N], a[N];
int C[N];
int lowbit(int x) {
   return x & -x;
}
void add(int x, int k) {
   for(; x < N; x += lowbit(x)) C[x] += k;
}
int query(int x) {
   int summ = 0;
   for(; x; x -= lowbit(x)) summ += C[x];
   return summ;
}
void cdq_per(int l, int r) {
   if(l == r) return ;
   int mid = (l + r) >> 1;
   cdq_per(l, mid); cdq_per(mid + 1, r);
   node p[N];
   int i = l, j = mid + 1, pt = 0;
   while(i <= mid && j <= r) {
   	if(e[i].x <= e[j].x) add(e[i].y, 1), p[++pt] = e[i++];
   	else {
   		if(e[j].type == 0) p[++pt] = e[j++];
   		else ansper[e[j].type] += query(N - 1) - query(e[j].y), p[++pt] = e[j++];
   	}
   }
   while(i <= mid) add(e[i].y, 1), p[++pt] = e[i++];
   while(j <= r) {
   	if(e[j].type == 0) p[++pt] = e[j++];
   	else ansper[e[j].type] += query(N - 1) - query(e[j].y), p[++pt] = e[j++];
   }
   for(int k = l; k <= mid; k++) add(e[k].y, -1);
   for(int t = 1, u = l; t <= pt && u <= r; t++, u++) e[u] = p[t];
}
void cdq_nxt(int l, int r) {
   if(l == r) return ;
   int mid = (l + r) >> 1;
   cdq_nxt(l, mid); cdq_nxt(mid + 1, r);
   node p[N];
   int i = l, j = mid + 1, pt = 0;
   while(i <= mid && j <= r) {
   	if(a[i].x <= a[j].x) add(a[i].y, 1), p[++pt] = a[i++];
   	else {
   		if(a[j].type == 0) p[++pt] = a[j++];
   		else ansnxt[a[j].type] += query(a[j].y), p[++pt] = a[j++];
   	}
   }
   while(i <= mid) add(a[i].y, 1), p[++pt] = a[i++];
   while(j <= r) {
   	if(a[j].type == 0) p[++pt] = a[j++];
   	else ansnxt[a[j].type] += query(a[j].y), p[++pt] = a[j++];
   }
   for(int k = l; k <= mid; k++) add(a[k].y, -1);
   for(int t = 1, u = l; t <= pt && u <= r; t++, u++) a[u] = p[t];
}
int main() {
//	freopen("data.in", "r", stdin);
   scanf("%d%d", &n, &m);
   for(int i = 1, x; i <= n; i++) {
   	scanf("%d", &x);
   	e[i].x = e[i].type = 0; e[i].y = x;
   	pos[x] = i;
   }
   for(int i = 1, x; i <= m; i++) {
   	scanf("%d", &x);
   	e[pos[x]].x = m - i + 1;
   	e[pos[x]].type = i;
   }
   for(int i = 1; i <= n; i++) a[i] = e[n - i + 1];
   cdq_per(1, n);
   for(int i = 1; i <= n; i++) e[i] = a[n - i + 1];
   cdq_nxt(1, n);
   lld ans_sum = 0;
   for(int i = 1; i <= n; i++) {
   	ans_sum += i - query(e[i].y) - 1;
   	add(e[i].y, 1);
   }
   int k = 1;
   while(k <= m) {
   	printf("%lld\n", ans_sum);
   	ans_sum -= (ansper[k] + ansnxt[k]);
   	k++;
   }
   return 0;
}
例题2 : P4169 [Violet]天使玩偶/SJY摆棋子
。。。。。
CDQ分治笔记+例题的更多相关文章
- BZOJ1173 CDQ分治 笔记
		
目录 二维数据结构->cdq 预备知识 T1: 二维树状数组 T2:cdq分治 bzoj1176 mokia:Debug心得 一类特殊的CDQ分治 附: bzoj mokia AC代码 二维数据 ...
 - CDQ分治入门 + 例题 Arnooks's Defensive Line [Uva live 5871]
		
CDQ分治入门 简介 CDQ分治是一种特别的分治方法,它由CDQ(陈丹琦)神犇于09国家集训队作业中首次提出,因此得名.CDQ分治属于分治的一种.它一般只能处理非强制在线的问题,除此之外这个算法作为某 ...
 - CDQ分治笔记
		
以前一直不会CDQ……然后经常听到dalao们说“这题直接CDQ啊”“CDQ不就秒了吗”的时候我只能瑟瑟发抖QAQ CDQ分治 其实CDQ分治就是二分分治,每次将$[l,r]$的问题划分为$[l,mi ...
 - cdq分治 笔记
		
算法讲解 这个算法用于解决三维偏序问题. 三维偏序:给定 \(n\) 个三元组: \((a_i,b_i,c_i)\),求同时满足满足 \(a_i\le a_j,b_i\le b_j,c_i\le c_ ...
 - 陌上花开——CDQ分治
		
传送门 “CDQ分治”从来都没有听说过,写了这题才知道还有这么神奇的算法. (被逼无奈).w(゚Д゚)w 于是看了不少dalao的博客,对CDQ算法粗浅地了解了一点.(想要了解CDQ的概念,可以看下这 ...
 - 点分治&cdq分治 总结
		
游荡的孤高灵魂不需要羁绊之处. 洛谷题单 点分治 前置芝士 树的重心 树分治 例题略解 P3806 [模板]点分治1 板子题,先暴力找到整棵树的重心,然后先求出重心到各点的距离,进而算出他所在树的各个 ...
 - 一篇自己都看不懂的CDQ分治&整体二分学习笔记
		
作为一个永不咕咕咕的博主,我来更笔记辣qaq CDQ分治 CDQ分治的思想还是比较简单的.它的基本流程是: \(1.\)将所有修改操作和查询操作按照时间顺序并在一起,形成一段序列.显然,会影响查询操作 ...
 - 学习笔记 | CDQ分治
		
目录 前言 啥是CDQ啊(它的基本思想) 例题 后记 参考博文 前言 博主太菜了 学习快一年的OI了 好像没有什么会的算法 更寒碜的是 学一样还不精一样TAT 如有什么错误请各位路过的大佬指出啊感谢! ...
 - 【教程】简易CDQ分治教程&学习笔记
		
前言 辣鸡蒟蒻__stdcall终于会CDQ分治啦! CDQ分治是我们处理各类问题的重要武器.它的优势在于可以顶替复杂的高级数据结构,而且常数比较小:缺点在于必须离线操作. CDQ分治的基 ...
 
随机推荐
- Win10如何关闭最近使用文件夹
			
如果默认开启了最近使用文件夹功能的话,最近浏览的文件就会被记录下来.所以看完各种教学资料后,并不是关掉或者重启别人就不知道你看了什么. 方法一: Win10怎么关闭最近使用文件夹 如何清理使用痕迹 从 ...
 - 深入NodeJS模块os - 与操作系统“打交道”
			
读了 os 模块的文档,研究了几个有意思的问题:
 - 使用Airtest进行UI自动化测试
			
一.环境搭建 1.Airtest客户端下载 访问官网http://airtest.netease.com/,根据自己的系统下载相应的客户端安装: 2.python工具下载与环境搭建 在本地python ...
 - Spring Cloud服务注册中心交付至kubernetes
			
前言 服务发现原则: 各个微服务在启动时,会将自己的网络地址等信息注册到服务发现组件中,服务发现组件会存储这些信息 服务消费者可以从服务发现组件中查询到服务提供者的网络地址,并使用该地址来远程调用服务 ...
 - jdk8中CompletableFuture的各个API用法,极大扩展了Future
			
就不介绍了,直接贴代码,建议在代码中使用,真的很方便 package cn.hou.completablefuture; import org.junit.Test; import java.util ...
 - LeetCode 18: 4 Sum 寻找4数和
			
链接 4Sum 难度 Medium 描述 Given an array nums of n integers and an integer target, are there elements a , ...
 - DOCKER 学习笔记4 认识DockerCompose 多容器编排
			
前言 通过上一节的学习,学会了如何在Linux 环境下搭建Docker并且部署Springboot 项目,并且成功的跑了起来,当然,在生产环境中,不只是需要一个后端的Web 项目,还需要比如 Ngin ...
 - ROS中3D机器人建模(四)
			
一.创建一个7-DOF机械臂机器人 创建一个名为seven_dof_arm.xacro的文件,写入相应的代码,其关节名称如下: bottom_joint shoulder_pan_joint shou ...
 - python中的变量和字符串
			
一.变量 1.python变量 *变量用于存储某个或某些特定的值,它与一个特定标识符相关联,该标识符称为变量名称.变量名指向存储在内存中的值.在创建变量时会在内存中开辟一个空间.基于变量的数据类型,解 ...
 - 实验19:Frame-Relay
			
实验16-1. 帧中继多点子接口 Ø 实验目的通过本实验,读者可以掌握如下技能:(1) 帧中继的基本配置(2) 帧中继的静态映射(3) 多点子接口的应用Ø 实验拓扑 实验步骤n 步骤1 ...