初学cdq分治学习笔记(可能有第二次的学习笔记)
前言骚话
本人蒟蒻,一开始看到模板题就非常的懵逼,链接,学到后面就越来越清楚了。
吐槽,cdq,超短裙分治。。。。(尴尬)
正片开始
思想
和普通的分治,还是分而治之,但是有一点不一样的是一般的分治在合并问题答案是,左右区间是分开来的,也就是左区间的答案不会对右区间的答案造成贡献,但是cdq分治要处理的就是左区间对于右区间的答案。
很多情况下,cdq分治都可以解决掉一维的答案,简单的来说就是直接去掉一个嵌套的数据结构,简直将代码量降至低谷,但是有一个很明显的缺点就是只能实现离线操作。QwQ
还是和题目一起将比较好,我们从\(2\)维偏序一直讲到n维偏序吧,(滑稽)
那么考虑偏序性的问题,最重要的是要保证答案的正确性,因为当前的处理不能影响到后面的状态。
二维偏序:第一维:排序解决,第二维:归并排序cdq或者是树状数组都可以,虽然有一点超出了cdq的范围,但是还是可以用cdq来实现的。
三维偏序:第一维:排序解决,第二维:cdq分治,第三维:树状数组。还有一种写法就是后两维用树套树来做。这个时候就非常明显可以体现出cdq分治的优越性了。树套树:代码直奔100行,cdq一般是不会超过70行。
详细讲一下:https://www.cnblogs.com/chhokmah/p/10571403.html,注意这里讲的不怎么详细。
首先将第一维排序,因为不是逆序对,那么就不需要维护编号,然后将所有一样的数都合并起来,因为我们也是要统计所有相同的个数。那么我们开始第二维的操作,因为有了第一维的顺序的限制,那么我们就不能随意的查找,这时候我们就是要开始求逆序对了。还是将现在区间进行排序,从小到大,这样我们分治,在将所有右区间内遍历一遍,如果右区间中有y小于左区间内的数,那么就在当前这个z上加上个数,表示这一段都是由答案的贡献,然后将答案统计在新的数组中就可以了。
#include <bits/stdc++.h>
#define ll long long
#define ms(a, b) memset(a, b, sizeof(a))
#define inf 0x3f3f3f3f
using namespace std;
template <typename T>
inline void read(T &x) {
x = 0; T fl = 1;
char ch = 0;
while (ch < '0' || ch > '9') {
if (ch == '-') fl = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = (x << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
x *= fl;
}
#define N 100005
struct data {
int x, y, z, res, cnt;
data() {
x = y = z = res = cnt = 0;
}
}a[N], v[N];
int n, k;
int ans[N];
struct BIT{
#define lowbit(x) (x&-x)
int n, tr[N];
void add(int x, int val) {
for (; x <= n; x += lowbit(x)) tr[x] += val;
}
int query(int x) {
int res = 0;
for (; x; x -= lowbit(x)) res += tr[x];
return res;
}
}tr;
bool cmp1(const data &a, const data &b) {
if (a.x == b.x)
if (a.y == b.y) return a.z < b.z;
else return a.y < b.y;
else return a.x < b.x;
}
bool cmp2(const data &a, const data &b) {
if (a.y == b.y) return a.z < b.z;
else return a.y < b.y;
}
void cdq(int l, int r) {
if (l == r) return;
int mid = (l + r) >> 1;
cdq(l, mid);
cdq(mid + 1, r);
sort(v + l, v + mid + 1, cmp2);
sort(v + mid + 1, v + r + 1, cmp2);
int l1 = l, l2 = mid + 1;
while (l2 <= r) {
while (l1 <= mid && v[l1].y <= v[l2].y)
tr.add(v[l1].z, v[l1].cnt), l1 ++;
v[l2].res += tr.query(v[l2].z);
l2 ++;
}
for (int i = l; i < l1; i ++) tr.add(v[i].z, -v[i].cnt);
}
int main() {
read(n); read(k);
tr.n = k;
for (int i = 1; i <= n; i ++) {
read(a[i].x); read(a[i].y); read(a[i].z);
}
sort(a + 1, a + 1 + n, cmp1);
int tot = 0;
for (int i = 1, j = 1; i <= n; i = j) {
v[++ tot] = a[i];
while (a[i].x == a[j].x && a[i].y == a[j].y && a[i].z == a[j].z && j <= n)
j ++, v[tot].cnt ++;
}
cdq(1, tot);
for (int i = 1; i <= tot; i ++)
ans[v[i].res + v[i].cnt] += v[i].cnt;
for (int i = 1; i <= n; i ++) printf("%d\n", ans[i]);
return 0;
}
关于四位偏序:第一维:排序,第二维:cdq分治,第三维:套一个cdq分治,第四位,树状数组。
和上面的思路差不多,只是代码更加冗长,但是想到树套树套树可能要飙到200+就感觉到非常欣慰。
没有时间写,就不写了。
关于五维偏序:我选择\(O(n^2)\)。这样可能更快。如果是用cdq来做的话,复杂度目测是\({log_2^n}^4\),而且代码复杂度为cdq套cdq套树套树,想想就可怕。
对于cdq分治的注意事项:千万不要用复杂度大的c++自带函数,比如说是memset,这样你会找都找不到自己错在哪里。
为了简化时间复杂度与常数,有几种方法可以参考:
(1)在分治之前先按照某一关键字排序,之后在分治过程中,将信息按照时间分成前后两部分,这样避免了多次排序。
(2)在分治过程中,利用归并排序的方式将两个有序序列合并,将O(nlog)的排序变为O(n)的归并。
(3)在分治过程中,利用树状数组解决问题,除非必须用到别的东西。
(4)在分治过程中,利用有序的性质可以发现,逆序也是有序的,并且满足一些正好与正序相反,这样可以避免重复排序。
(5)在分治之前尽可能的简化不必要的信息,这样能减少整个代码的常数。
(6)另外,在更新右区间或者合并的时候,尽量选择常数与时间复杂度较小的算法,比如说能用单调队列就不要用斜率优化,能用斜率优化就不要用决策单调性。
#include <bits/stdc++.h>
#define ll long long
#define N 100005
using namespace std;
struct BIT{
#define lowbit(x) (x&-x)
int n, tr[N];
void add(int x, int val) {
for (; x <= n; x += lowbit(x)) tr[x] += val;
}
int query(int x) {
int res = 0;
for (; x; x -= lowbit(x)) res += tr[x];
return res;
}
}tr;
struct Que {
int cnt, v, d, id, t;
}q[N << 1];
int n, m;
ll ans[N];
int a[N], pos[N];
bool cmp(const Que &a, const Que &b) {
return a.d < b.d;
}
void cdq(int l, int r) {
if (l == r) return;
int mid = (l + r) >> 1;
cdq(l, mid);
cdq(mid + 1, r);
sort(q + l, q + mid + 1, cmp);
sort(q + mid + 1, q + 1 + r, cmp);
int l1 = l, l2 = mid + 1;
while (l2 <= r) {
while (l1 <= mid && q[l1].d <= q[l2].d) tr.add(q[l1].v, q[l1].cnt), ++ l1;
ans[q[l2].id] += q[l2].cnt * (tr.query(n) - tr.query(q[l2].v));
l2 ++;
}
for (int i = l; i < l1; i ++) tr.add(q[i].v, -q[i].cnt);
l1 = r; l2 = mid;
while (l1 > mid) {
while (l2 >= l && q[l2].d >= q[l1].d) tr.add(q[l2].v, q[l2].cnt), -- l2;
ans[q[l1].id] += q[l1].cnt * tr.query(q[l1].v - 1);
l1 --;
}
for (int i = mid; i > l2; i --) tr.add(q[i].v, -q[i].cnt);
}
int main() {
scanf("%d%d", &n, &m);
int tot = 0;
tr.n = n;
for (int i = 1; i <= n; i ++) {
scanf("%d", &a[i]);
pos[a[i]] = i;
q[++ tot] = (Que){1, a[i], i, 0, tot};
}
for (int i = 1; i <= m; i ++) {
int x; scanf("%d", &x);
q[++ tot] = (Que){-1, x, pos[x], i, tot};
}
cdq(1, tot);
for (int i = 1; i <= m; i ++) ans[i] += ans[i - 1];
for (int i = 0; i < m; i ++) printf("%lld\n", ans[i]);
return 0;
}
初学cdq分治学习笔记(可能有第二次的学习笔记)的更多相关文章
- 初学CDQ分治-NEU1702
关于CDQ分治,首先需要明白分治的复杂度. T(n) = 2T(n/2)+O(kn), T(n) = O(knlogn) T(n) = 2T(n/2)+O(knlogn), T(n) = O(knlo ...
- 【学习笔记】CDQ分治(等待填坑)
因为我对CDQ分治理解不深,所以这篇博客只是我现在的浅显理解有任何不对的,希望大佬指出. 首先就是CDQ分治适用的题型: (1)带修改,但修改互相独立 (2)必须允许离线 (3)解决数据结构的题,能把 ...
- ACdream1157 Segments(CDQ分治 + 线段树)
题目这么说的: 进行如下3种类型操作:1)D L R(1 <= L <= R <= 1000000000) 增加一条线段[L,R]2)C i (1-base) 删除第i条增加的线段, ...
- 学习笔记 | CDQ分治
目录 前言 啥是CDQ啊(它的基本思想) 例题 后记 参考博文 前言 博主太菜了 学习快一年的OI了 好像没有什么会的算法 更寒碜的是 学一样还不精一样TAT 如有什么错误请各位路过的大佬指出啊感谢! ...
- 一篇自己都看不懂的CDQ分治&整体二分学习笔记
作为一个永不咕咕咕的博主,我来更笔记辣qaq CDQ分治 CDQ分治的思想还是比较简单的.它的基本流程是: \(1.\)将所有修改操作和查询操作按照时间顺序并在一起,形成一段序列.显然,会影响查询操作 ...
- [学习笔记] CDQ分治 从感性理解到彻底晕菜
最近学了一种叫做CDQ分治的东西...用于离线处理一系列操作与查询似乎跑得很快233 CDQ的名称似乎源于金牌选手陈丹琦 概述: 对于一坨操作和询问,分成两半,单独处理左半边和处理左半边对于右半边的影 ...
- [偏序关系与CDQ分治]【学习笔记】
组合数学真是太棒了 $CDQ$真是太棒了(雾 参考资料: 1.<组合数学> 2.论文 课件 很容易查到 3.sro __stdcall 偏序关系 关系: 集合$X$上的关系是$X$与$X$ ...
- CDQ分治与整体二分学习笔记
CDQ分治部分 CDQ分治是用分治的方法解决一系列类似偏序问题的分治方法,一般可以用KD-tree.树套树或权值线段树代替. 三维偏序,是一种类似LIS的东西,但是LIS的关键字只有两个,数组下标和 ...
- CDQ分治学习笔记
数据结构中的一块内容:$CDQ$分治算法. $CDQ$显然是一个人的名字,陈丹琪(NOI2008金牌女选手) 这种离线分治算法被算法界称为"cdq分治" 我们知道,一个动态的问题一 ...
随机推荐
- offic|集成|协同OA|移动办公|
随着互联网时代的日新月异,移动通讯技术的飞速发展,移动网络技术的更新换代,手机.平板电脑等移动设备越来越智能化.越来越多样化,人们对移动办公的需求也在日益增长.在此背景下北京博信施科技有限公司自主研发 ...
- Python开发爬虫之BeautifulSoup解析网页篇:爬取安居客网站上北京二手房数据
目标:爬取安居客网站上前10页北京二手房的数据,包括二手房源的名称.价格.几室几厅.大小.建造年份.联系人.地址.标签等. 网址为:https://beijing.anjuke.com/sale/ B ...
- Easyui 修改jquery validatebox为英文校验提示为中文提示
修改jquery validatebox为英文校验提示为中文提示 by:授客 QQ:1033553122 测试环境 jquery-easyui-1.5.3 问题描述: 如图,想把校验提示由英文改成中文 ...
- 转摘Linux命令工作中常用总结
Linux命令工作中常用总结 1. 搜索 在vi和vim中如果打开一个很大的文件,不容易找到对应的内容,可以使用自带的搜索关键字进行搜索定位: 在vi和vim界面中输入:"/" ...
- UE3中的时间
为了管理时间,Unreal将游戏运行时间片分隔为"Ticks".一个Tick是关卡中所有Actors更新的最小时间单位.一个tick一般是10ms-100ms(CPU性能越好,游戏 ...
- ASP.NET没有魔法——ASP.NET MVC IoC代码篇
上一篇文章主要以文字的形式介绍了IoC及其在ASP.NET MVC中的使用,本章将从以下几点介绍如何使用代码在ASP.NET MVC中实现依赖注入: ● AutoFac及安装 ● 容器的创建 ● 创建 ...
- org.springframework.beans.factory.NoUniqueBeanDefinitionException 导致原因之一
导致此异常原因很多,以下仅是针对其中一种因素的解决办法. 下面是DAO接口.Service接口.Service实现类的全路径名(全部定义在com.xxx.projetc包下) com.xxx.proj ...
- Python基础——2函数
函数 函数定义 def a(参数): 函数的参数:必选参数.默认参数.可变参数.命名关键字参数和关键字参数 a(x,y,z=11,*l,**b): a(x,y,z=11,*liat,city,job, ...
- 【Python 20】BMR计算器4.0(异常处理)
1.案例描述 基础代谢率(BMR):我们安静状态下(通常为静卧状态)消耗的最低热量,人的其他活动都建立在这个基础上. 计算公式: BMR(男) = (13.7*体重kg)+(5.0*身高cm)-(6. ...
- c# 正则验证
1.验证百分数 bool tempBool = Regex.IsMatch(str, @"[1-9]{0,1}[0-9](\\.[0-9])?%");