N维偏序:cdq分治
cdq(陈丹琦)分治,是一种类似二分的算法。基本思想同分治:
- 递归,把大问题划分成若干个结构相同的子问题,直到(L==R);
- 处理左区间[L,mid]对右区间[mid+1,R]的影响;
- 合并。
它可以顶替复杂的高级数据结构,但必须离线操作。
N维偏序,就是求N个关键字下的顺/逆序对。cdq分治是这类题中常用的降维手段。
一维偏序
学习归并排序时,我们了解到它的一个特性就是可以用来求逆序对。
- void merge(int L,int R) {
- if(L == R)return;
- int mid = (L+R)/;
- merge(L,mid);
- merge(mid+,R);
- int idx = L;
- int i = L,j = mid+;
- while(i <= mid&&j <= R) {
- if(a[i] <= a[j])temp[idx++] = a[i++];
- else {
- temp[idx++] = a[j++];
- cnt += mid-i+;
- }
- }
- while(i <= mid)temp[idx++] = a[i++];
- while(j <= R)temp[idx++] = a[j++];
- for(int i = L; i <= R; i++)
- a[i] = temp[i];
- }
归并排序求逆序对
考虑它的原理:只统计对于右面的每一个元素,左边比它大的。
两边的数列都为有序,且各自的逆序对都已经统计完了。
那么对于右边的第j个元素(j>=mid+1),如果左边的第i个元素比j大,那么i+1,i+2....到mid一定都比j大。
这里就体现了cdq分治的思想,也是多维偏序的基础。可以说,归并排序求逆序对是cdq分治的一个特例。
二维偏序
除了归并排序,一维偏序也可以用树状数组解决。实际上,一部分树状数组能解决的问题,cdq分治也可以解决。
- #include<iostream>
- #include<cstdio>
- #include<cstring>
- #define MogeKo qwq
- using namespace std;
- const int maxn = ;
- int n,m,opt,x,y,sum[maxn];
- int lowbit(int x){
- return x & -x;
- }
- void update(int x,int k){
- while(x <= n){
- sum[x] += k;
- x += lowbit(x);
- }
- }
- int query(int x){
- int ans = ;
- while(x){
- ans += sum[x];
- x -= lowbit(x);
- }
- return ans;
- }
- int main(){
- scanf("%d%d",&n,&m);
- for(int i = ;i <= n;i++){
- scanf("%d",&y);
- update(i,y);
- }
- for(int i = ;i <= m;i++){
- scanf("%d%d%d",&opt,&x,&y);
- if(opt == )update(x,y);
- if(opt == )printf("%d\n",query(y)-query(x-));
- }
- return ;
- }
树状数组
树状数组板子题,可以轻松解决。
把它转化为二维偏序问题,对于每个修改和询问,都有(时间,位置)两个维度。
开一个结构体q[],数组下标记录时间,q[].id记录位置,q[].type记录类型(修改或询问)。注意,当修改和询问在同一位置时,修改操作要优先。
解决二维偏序问题首先需要控制一维有序,另一维进行归并排序。在这里,时间默认就是有序的(++cnt);
对于每个修改操作,记录修改的元素位置。数组赋初值的方式和修改操作相同,可以当做时间在最前的修改。
查询怎么办?用树状数组求一段区间和时,需要用到前缀和,即 R-(L-1)。
那么,询问的位置也可以拆分成两个:(L-1)和 R。用不同的type来区分它们:( L-1的要减去,R的要加上)。
如何进行归并排序?对于一段位置有序的区间,一定是时间在前的修改操作会影响时间在后的查询操作。
用sum维护区间内修改操作的值,修改时用sum+修改值;
用ans记录询问的答案,ans -所有(L-1)的sum +所有R的sum 即为这个询问的结果。为啥非要用cdq分治啊麻烦死了QAQ!!!
- #include<iostream>
- #include<cstdio>
- #include<cstring>
- #define MogeKo qwq
- using namespace std;
- const int maxn = *;
- int n,m,cnt,cqry,opt,x,y,ans[maxn];
- struct node{
- int type,id,val;
- bool operator < (const node & x) const{
- if(id != x.id)return id < x.id;
- else return type < x.type;
- }
- }q[maxn],tem[maxn];
- void cdq(int L,int R){
- if(L == R) return;
- int mid = L+R>>;
- cdq(L,mid),cdq(mid+,R);
- int t1 = L,t2 = mid+;
- int sum = ;
- for(int i = L;i <= R;i++){
- if( (t1 <= mid && q[t1]<q[t2]) || t2 > R){
- if(q[t1].type == ) sum += q[t1].val;
- tem[i] = q[t1++];
- }
- else{
- if(q[t2].type == ) ans[q[t2].val] -= sum;
- if(q[t2].type == ) ans[q[t2].val] += sum;
- tem[i] = q[t2++];
- }
- }
- for(int i = L;i <= R;i++) q[i] = tem[i];
- }
- int main(){
- scanf("%d%d",&n,&m);
- for(int i = ;i <= n;i++){
- cnt++;
- scanf("%d",&y);
- q[cnt].type = ;
- q[cnt].id = i;
- q[cnt].val = y;
- }
- for(int i = ;i <= m;i++){
- scanf("%d%d%d",&opt,&x,&y);
- if(opt == ){
- q[++cnt].type = ;
- q[cnt].id = x;
- q[cnt].val = y;
- }
- if(opt == ){
- cqry++;
- q[++cnt].type = ;
- q[cnt].id = x-;
- q[cnt].val = cqry;
- q[++cnt].type = ;
- q[cnt].id = y;
- q[cnt].val = cqry;
- }
- }
- cdq(,cnt);
- for(int i = ;i <= cqry;i++)
- printf("%d\n",ans[i]);
- return ;
- }
二维偏序
三维偏序
扩展到三维。设三维分别为x,y,z
先按x排序,消除第一维的影响。
考虑不使用cdq,用一个树状数组维护第二维,另一个树状数组维护第三维...就会出现树套树的神奇情况
模仿之前的做法,第二维使用cdq分治,按y进行归并排序。虽然x的顺序被打乱了,但左一半一定小于右一半。第二维的影响被消除了。
第三维可以用一个权值树状数组维护。
- int t1=L, t2=mid+;
- while(t2 <= R){
- while(t1 <= mid && b[t1].y <= b[t2].y){
- tree.update(b[t1].z,b[t1].num);
- t1++;
- }
- b[t2].ans += tree.query(b[t2].z);
- t2++;
- }
已经控制x2>x1,将所有y1<y2时按z1把当前花的个数加入树状数组,再查询比z2小的在树状数组中有多少个。
由于归并排序时,y2后的y3一定大于y1,所以已经加入的z的个数不用清空。
当归并的操作结束时,再把树状数组减去已经加入的左区间的z的个数(也就是左区间指针t1之前)。
提供的数据中,可能有xyz完全相同的情况,所以初始化时要先去重,但不能直接调用unique函数。统计相同的花的个数,用结构体的.num记录。
这样当把花按x加入树状数组时,加入.num中的个数就可以了。
- #include<cstdio>
- #include<cstring>
- #include<algorithm>
- #define MogeKo qwq
- using namespace std;
- const int maxn = ;
- int n,m,k,cnt[maxn];
- struct node{
- int x,y,z,num,ans;
- bool operator < (const node & A) const {
- return x<A.x || (x==A.x && (y<A.y || (y==A.y && z<A.z)));
- }
- bool operator == (const node & A) const {
- return x==A.x && y==A.y && z==A.z;
- }
- }a[maxn],b[maxn];
- bool cmpyz(node A,node B){
- return A.y<B.y || (A.y==B.y && A.z<B.z);
- }
- struct BIT{
- int sum[maxn],len;
- int lowbit(int x){
- return x & -x;
- }
- void update(int x,int k){
- for(int i = x; i<=len; i+=lowbit(i))
- sum[i] += k;
- }
- int query(int x){
- int ans = ;
- for(int i = x; i; i-=lowbit(i))
- ans += sum[i];
- return ans;
- }
- }tree;
- void cdq(int L,int R){
- if(L == R)return;
- int mid = L+R>>;
- cdq(L,mid),cdq(mid+,R);
- sort(b+L,b+mid+,cmpyz);
- sort(b+mid+,b+R+,cmpyz);
- int t1=L, t2=mid+;
- while(t2 <= R){
- while(t1 <= mid && b[t1].y <= b[t2].y){
- tree.update(b[t1].z,b[t1].num);
- t1++;
- }
- b[t2].ans += tree.query(b[t2].z);
- t2++;
- }
- for(int i = L;i <= t1-;i++)
- tree.update(b[i].z,-b[i].num);
- }
- int main(){
- scanf("%d%d",&n,&k);
- tree.len = k;
- for(int i = ;i <= n;i++)
- scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].z);
- sort(a+,a+n+);
- int bcnt = ;
- for(int i = ;i <= n;i++){
- bcnt++;
- if(a[i]==a[i+])continue;
- b[++m] = a[i], b[m].num = bcnt;
- bcnt = ;
- }
- cdq(,m);
- for(int i = ;i <= m;i++)
- cnt[b[i].ans+b[i].num-] += b[i].num;
- for(int i = ;i <= n-;i++)
- printf("%d\n",cnt[i]);
- return ;
- }
三维偏序
其实cdq分治我也不是很明白qwq
理论上,cdq分治可以解决任意N维偏序问题。但是,cdq套cdq的复杂度会达到n logkn,当它超过n2的时候...还是选择暴力枚举吧w
N维偏序:cdq分治的更多相关文章
- Luogu 3810 & BZOJ 3262 陌上花开/三维偏序 | CDQ分治
Luogu 3810 & BZOJ 3263 陌上花开/三维偏序 | CDQ分治 题面 \(n\)个元素,每个元素有三个值:\(a_i\), \(b_i\) 和 \(c_i\).定义一个元素的 ...
- COGS 2479. [HZOI 2016]偏序 [CDQ分治套CDQ分治 四维偏序]
传送门 给定一个有n个元素的序列,元素编号为1~n,每个元素有三个属性a,b,c,求序列中满足i<j且ai<aj且bi<bj且ci<cj的数对(i,j)的个数. 对于100%的 ...
- [COGS2479 && COGS2639]高维偏序(CDQ分治,bitset)
COGS2479:四维偏序. CDQ套CDQ CDQ:对a分治,对b排序,再对a打标记,然后执行CDQ2 CDQ2:对b分治,对c归并排序,对d树状数组. #include<cstdio> ...
- BZOJ3262 陌上花开 —— 三维偏序 CDQ分治
题目链接:https://vjudge.net/problem/HYSBZ-3262 3262: 陌上花开 Time Limit: 20 Sec Memory Limit: 256 MBSubmit ...
- bzoj3262: 陌上花开 三维偏序cdq分治
三维偏序裸题,cdq分治时,左侧的x一定比右侧x小,然后分别按y排序,对于左侧元素按y大小把z依次插入到树状数组里,其中维护每个左侧元素对右侧元素的贡献,在bit查询即可 /************* ...
- [bzoj] 3263 陌上花开 洛谷 P3810 三维偏序|| CDQ分治 && CDQ分治讲解
原题 定义一个点比另一个点大为当且仅当这个点的三个值分别大于等于另一个点的三个值.每比一个点大就为加一等级,求每个等级的点的数量. 显然的三维偏序问题,CDQ的板子题. CDQ分治: CDQ分治是一种 ...
- BZOJ 3295:[Cqoi2011]动态逆序对(三维偏序 CDQ分治+树状数组)
http://www.lydsy.com/JudgeOnline/problem.php?id=3295 题意:简单明了. 思路:终于好像有点明白CDQ分治处理三维偏序了.把删除操作看作是插入操作,那 ...
- 三维偏序[cdq分治学习笔记]
三维偏序 就是让第一维有序 然后归并+树状数组求两维 cdq+cdq不会 告辞 #include <bits/stdc++.h> // #define int long long #def ...
- [偏序关系与CDQ分治]【学习笔记】
组合数学真是太棒了 $CDQ$真是太棒了(雾 参考资料: 1.<组合数学> 2.论文 课件 很容易查到 3.sro __stdcall 偏序关系 关系: 集合$X$上的关系是$X$与$X$ ...
随机推荐
- 腾讯.NET&PHP面试题
在整个面试过程中,作为面试者的你,角色就是小怪兽,面试官的角色则是奥特曼,更不幸的是,作为小怪兽的你是孤身一人,而奥特曼却往往有好几个助攻,你总是被虐得不要不要的~ 作为复读一年才考上专科的我,遗憾的 ...
- listview reclyerview上下拉刷新
x写控件挺麻烦的,因为有很多细节要处理好,列表控件使用太频繁了,网上也各种自定义的方法,一般的listview自定义肯定会联想到加个头部,然后监听事件加动画,其实方式很多种,今天记录的方式是另外一种方 ...
- Android Dialog对话框
Dialog的基本方法 //创建Dialog AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this); //设 ...
- c++屏蔽Win10系统快捷键
很久之前实现的功能,也是参考其他人的实现,时间太久,具体参考哪里已经记不得了. 这里不仅能屏蔽一般的快捷键,还可以屏蔽ctrl+atl+del. ; HHOOK keyHook = NULL; HHO ...
- PyCharm 在PyCharm中使用GitHub
PyCharm是当前进行Python开发,尤其是Django开发最好的IDE,GitHub是程序员的圣地,几乎人人都在用,就不详细介绍两者了. 本文假设你对PyCharm和Github都有一定的了解, ...
- 解决终端SSH连接服务器一段时间不操作之后卡死的问题
卡死是因为LIUNX安全设置问题,在一段时间内没有使用数据的情况下会自动断开,解决方法就是让本地或者服务器隔一段时间发送一个请求给对方即可 在本地打开配置文件(不建议在server端设置) sudo ...
- LeetCode算法题-Pascal's Triangle II(Java实现)
这是悦乐书的第171次更新,第173篇原创 01 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第30题(顺位题号是119).给定非负索引k,其中k≤33,返回Pascal三角形的第k ...
- MATLAB中冒号的用法
MATLAB中冒号的用法 作者:凯鲁嘎吉 - 博客园 http://www.cnblogs.com/kailugaji/ matlab中冒号代表步长,用实例来说明 >> A=[1 2 3 ...
- 聚类——K-means
聚类——认识K-means算法 作者:凯鲁嘎吉 - 博客园 http://www.cnblogs.com/kailugaji/ 一.聚类与分类 聚类: 无监督学习.聚类是在预先不知道欲划分类的情况下, ...
- Windows下mysql服务的安装与卸载
安装 mysqld -install 也可以指定mysql安装服务的文件 my.ini文件配置好后就可以在cmd中安装mysqld服务了,在cmd中运行命令:mysqld --install MySQ ...