『cdq分治和多维偏序问题』
<更新提示>
<第一次更新>
<第二次更新>更新了三维偏序问题的拓展
<正文>
cdq分治
\(cdq\)分治是一种由\(IOI\ Au\)选手\(cdq\)提出的离线分治算法,又称基于时间的分治算法。
二维偏序问题
这是\(cdq\)分治最早提出的时候解决的问题,大意为:给定\(n\)对二元组\((a_{i},b_{i})\),求\(cnt_i=\sum_{j=i+1}^n[a_j>a_i\ and \ b_j>b_i]\)。
这个和逆序对有点像,先将二元组按\(a\)为第一关键字排序,那么\(a\)就一定有序了,即求下标和数值(\(b\))都比当前二元组(\(b\))大的二元组数量,和逆序对相反,就是正序对。
逆序对有一个很高效的\(O(nlog_2n)\)的求法,就是归并排序。当然,正序对也可以用归并排序做,其原理是类似的。
其实,这就是\(cdq\)分治的原型了。从分治的角度考虑,\(solve(l,r)\)对序列\([l,r]\)的部分进行求解,首先我们将\([l,r]\)分治为\([l,mid]\)和\([mid+1,r]\)这两部分,这是可以递归求解的。那么,我们要考虑的就是分别在区间\([l,mid]\)和\([mid+1,r]\)两边的元素构成的正序对数量。
怎么求呢?归并就可以了。在把两边的元素归并排序的过程中,左边的二元组的第一维(\(a\))显然还小于右边,但是在归并的过程中我们可以得知第二维\(b\)的关系,这样就方便统计答案了。
所以,求解二维偏序问题的\(cdq\)分治算法就是以归并排序求逆序对为原型的。
\(Code:\)(\(HLOJ\)炉石传说)
#include <bits/stdc++.h>
using namespace std;
const int N=200020;
int n,ans[N];
struct node{int a,b,id;};
node c[N],cur[N];
inline void input(void)
{
scanf("%d",&n);
for (int i=1;i<=n;i++)
scanf("%d%d",&c[i].a,&c[i].b) , c[i].id = i;
}
inline bool compare(node x,node y)
{
return x.a == y.a ? x.b < y.b : x.a < y.a;
}
inline void cdq(int l,int r)
{
if ( l == r ) return;
int mid = l+r >> 1 , p = l , q = mid+1 , t = l-1;
cdq(l,mid); cdq(mid+1,r);
while ( p <= mid && q <= r )
{
if ( c[p].b <= c[q].b )
cur[++t] = c[p++];
if ( c[p].b > c[q].b )
ans[ c[q].id ] += p-l , cur[++t] = c[q++];
}
while ( p <= mid ) cur[++t] = c[p++];
while ( q <= r ) ans[ c[q].id ] += mid-l+1 , cur[++t] = c[q++];
for (int i=l;i<=r;i++) c[i] = cur[i];
}
int main(void)
{
input();
sort( c+1 , c+n+1 , compare );
cdq(1,n);
for (int i=1;i<=n;i++)
printf("%d\n",ans[i]);
return 0;
}
二维偏序问题的拓展
Describetion
假设有一列数\({A_i }(1 ≤ i ≤ n)\) ,支持如下两种操作:
(1)将\(A_k\)的值加\(D\)。(\(k\),\(D\)是输入的数)
(2) 输出\(A_s+A_{s+1}+…+A_t\)。(\(s\),\(t\)都是输入的数,\(S≤T\))
根据操作要求进行正确操作并输出结果。
解析
这个很明显是线段树/树状数组模板题吧,当然,我们是可以转换为二维偏序问题来解决的。
我们将给出的指令分为两类:查询指令和修改指令。可以这样思考:对于一个查询指令,其结果等价于计算原始数列和之后一系列修改对该位置的影响。那么我们把它转换为二维偏序问题:二元组\((a,b)\)代表在时刻\(a\),位置\(b\)进行的指令,我们要知道所有时刻\(a\)以前,位置\(b\)以前修改指令对查询指令的影响,这就类似于正序对了。
重新定义分治函数\(solve(l,r)\)为计算时间\([l,r]\)中所有修改指令对查询指令的影响,和归并排序一样计算就可以了。
敲代码时候的一点小技巧:将区间和查询\([l,r]\)改为两个前缀和查询,其中一个为正影响,一个为负影响,这样就方便上述方法修改操作对查询操作影响的计算了。
\(Code:\)(\(HLOJ\)数列求和)
#include <bits/stdc++.h>
using namespace std;
const int N=100020,M=150020;
int n,m,cnt,tot;
long long ans[M];
struct order
{
int flag,pos;
long long val;
bool operator < (const order &t)const
{
return pos == t.pos ? flag < t.flag : pos < t.pos;
}
}a[N+2*M],cur[N+2*M];
inline void input(void)
{
scanf("%d",&n);
for (int i=1;i<=n;i++)
{
long long val;scanf("%lld",&val);
a[++cnt] = (order){1,i,val};
}
scanf("%d",&m);
for (int i=1;i<=m;i++)
{
char op[5];long long k1,k2;
scanf("%s%lld%lld",op,&k1,&k2);
if (op[0]=='A') a[++cnt] = (order){1,k1,k2};
if (op[0]=='S') a[++cnt] = (order){2,k2,++tot} , a[++cnt] = (order){3,k1-1,tot};
}
}
inline void cdq(int l,int r)
{
if ( l == r ) return;
int mid = l+r >> 1 , p = l , q = mid+1 , t = l-1 , sum = 0;
cdq(l,mid); cdq(mid+1,r);
while ( p <= mid && q <= r )
{
if ( a[p] < a[q] )
{
if ( a[p].flag == 1 ) sum += a[p].val;
cur[++t] = a[p++];
}
else
{
if ( a[q].flag == 2 ) ans[ a[q].val ] += sum;
if ( a[q].flag == 3 ) ans[ a[q].val ] -= sum;
cur[++t] = a[q++];
}
}
while ( p <= mid ) cur[++t] = a[p++];
while ( q <= r )
{
if ( a[q].flag == 2 ) ans[ a[q].val ] += sum;
if ( a[q].flag == 3 ) ans[ a[q].val ] -= sum;
cur[++t] = a[q++];
}
for (int i=l;i<=r;i++) a[i] = cur[i];
}
int main(void)
{
input();
cdq(1,cnt);
for (int i=1;i<=tot;i++)
printf("%lld\n",ans[i]);
return 0;
}
三维偏序
二维偏序的问题同样有升级版,只不过换成了三元组而已。
同样地,我们先排序保证第一维的有序性,然后对第二维进行\(cdq\)分治,只不过在更新的时候出了点问题:满足前两维\(a,b\)有序是还需满足第三维\(c\)有序,那么我们在值域范围上建立一个树状数组,统计每个\(c\)值的出现次数,然后查询前缀和就可以了。
回顾二维偏序问题,如果不用\(cdq\)分治,树状数组也能直接解决,在这题中,如果不用\(cdq\)分治,可以用树状数组套平衡树做。其实,\(cdq\)分治的好处就在于顶替了一层数据结构,这样就可以大大减少代码量和时间常数。
\(Code:\)(\(BZOJ\)陌上花开)
#include <bits/stdc++.h>
using namespace std;
const int N=102000,K=220000;
int n,k,ans[N],bucket[N];
struct BIT
{
int p[K];
inline int lowbit(int x){return x&(-x);}
inline void insert(int pos,int val)
{
for (;pos<=k;pos+=lowbit(pos))
p[pos] += val;
}
inline int query(int pos)
{
int res = 0;
for (;pos;pos-=lowbit(pos))
res += p[pos];
return res;
}
}tree;
struct flower{int a,b,c,id;};
flower f[N],cur[N];
inline bool compare(flower p1,flower p2)
{
if ( p1.a ^ p2.a ) return p1.a < p2.a;
if ( p1.b ^ p2.b ) return p1.b < p2.b;
return p1.c < p2.c;
}
inline bool equal(flower p1,flower p2)
{
return p1.a == p2.a && p1.b == p2.b && p1.c == p2.c;
}
inline int read(void)
{
int x = 0 , w = 0; char ch=' ';
while (!isdigit(ch)) w |= ch=='-' , ch = getchar();
while (isdigit(ch)) x = (x<<1) + (x<<3) + (ch^48) , ch = getchar();
return w ? -x : x;
}
inline void input(void)
{
n = read() , k = read();
for (register int i=1;i<=n;i++)
f[i].a = read() , f[i].b = read() , f[i].c = read() , f[i].id = i;
}
inline void init(void)
{
flower t;int cnt = 1;
for (register int i=n;i>=1;i--)
if (equal(t,f[i]))
ans[ f[i].id ] += cnt , cnt++;
else t = f[i] , cnt = 1;
}
inline void cdq(int l,int r)
{
if ( l == r ) return;
int mid = l+r >> 1 , p = l , q = mid+1 , t = l-1 ;
cdq(l,mid); cdq(mid+1,r);
while ( p <= mid && q <= r )
{
if ( f[p].b <= f[q].b )
{
tree.insert(f[p].c,1);
cur[++t] = f[p++];
}
if ( f[p].b > f[q].b )
{
ans[ f[q].id ] += tree.query(f[q].c);
cur[++t] = f[q++];
}
}
while ( q <= r ) ans[ f[q].id ] += tree.query(f[q].c) , cur[++t] = f[q++];
for (register int i=l;i<p;i++) tree.insert(f[i].c,-1);
while ( p <= mid ) cur[++t] = f[p++];
for (register int i=l;i<=r;i++) f[i] = cur[i];
}
int main(void)
{
input();
sort( f+1 , f+n+1 , compare );
init();
cdq(1,n);
for (register int i=1;i<=n;i++)
bucket[ ans[i] ]++;
for (register int i=0;i<n;i++)
printf("%d\n",bucket[i]);
return 0;
}
三维偏序问题的拓展
Describetion
有一个\(N*N\)矩阵,给出一系列的修改和询问,修改是这样的:将\((x,y)\)中的数字加上\(k\),而询问是这样的:求\((x_1,y_1)\)到\((x_2,y_2)\)这个子矩阵内所有数字的和。
解析
这个和二维偏序问题的拓展相似,将区间求和推广为了矩阵求和。但是其本质还是一样的,我们将一根矩阵求和的询问拆为四个二维前缀和的查询。对于二维前缀和的查询和单点的修改,我们将其转换为三维偏序问题:三元组\((t,x,y)\)代表在时刻\(t\),位置\((x,y)\)执行的指令,显然,当修改操作\((t',x',y')\)对操作操作\((t,x,y)\)有影响时,\(t'<t,x'\leq x,y'\leq y\),这就是经典的三维偏序问题。
我们结合上文"数列求和","陌上花开"两题的思路,利用\(cdq\)分治和树状数组就可以轻松的实现本题了。
\(Code:\)(\(HLOJ\)好朋友的题)
#include <bits/stdc++.h>
using namespace std;
const int N=2000020,M=800020;
int n,m,cnt,tot,ans[M];
struct BIT
{
int p[N];
inline int lowbit(int x){return x&(-x);}
inline void insert(int pos,int val)
{
for (;pos<=n;pos+=lowbit(pos))
p[pos] += val;
}
inline int query(int pos)
{
int res = 0;
for (;pos;pos-=lowbit(pos))
res += p[pos];
return res;
}
}tree;
struct order
{
int flag,x,y,val;
bool operator < (order p)
{
if ( x ^ p.x ) return x < p.x;
if ( y ^ p.y ) return y < p.y;
return flag < p.flag;
}
}a[M],cur[M];
inline void input(void)
{
int op,x1,y1,x2,y2,val;
scanf("%d",&n);
while (true)
{
scanf("%d",&op);
if ( op == 1 )
{
scanf("%d%d%d",&x1,&y1,&val);
a[++cnt] = (order){1,x1,y1,val};
}
if ( op == 2 )
{
scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
a[++cnt] = (order){2,x2,y2,++tot};
a[++cnt] = (order){2,x1-1,y1-1,tot};
a[++cnt] = (order){3,x1-1,y2,tot};
a[++cnt] = (order){3,x2,y1-1,tot};
}
if ( op == 3 ) break;
}
}
inline void cdq(int l,int r)
{
if ( l == r ) return;
int mid = l+r >> 1 , p = l , q = mid+1 , t = l-1;
cdq(l,mid); cdq(mid+1,r);
while ( p <= mid && q <= r )
{
if ( a[p] < a[q] )
{
if ( a[p].flag == 1 ) tree.insert(a[p].y,a[p].val);
cur[++t] = a[p++];
}
else
{
if ( a[q].flag == 2 ) ans[ a[q].val ] += tree.query(a[q].y);
if ( a[q].flag == 3 ) ans[ a[q].val ] -= tree.query(a[q].y);
cur[++t] = a[q++];
}
}
while ( q <= r )
{
if ( a[q].flag == 2 ) ans[ a[q].val ] += tree.query(a[q].y);
if ( a[q].flag == 3 ) ans[ a[q].val ] -= tree.query(a[q].y);
cur[++t] = a[q++];
}
for (int i=l;i<p;i++)
if ( a[i].flag == 1 )
tree.insert(a[i].y,-a[i].val);
while ( p <= mid ) cur[++t] = a[p++];
for (int i=l;i<=r;i++) a[i] = cur[i];
}
int main(void)
{
input();
cdq(1,cnt);
for (int i=1;i<=tot;i++)
printf("%d\n",ans[i]);
return 0;
}
<后记>
『cdq分治和多维偏序问题』的更多相关文章
- 【BZOJ-3262】陌上花开 CDQ分治(3维偏序)
3262: 陌上花开 Time Limit: 20 Sec Memory Limit: 256 MBSubmit: 1439 Solved: 648[Submit][Status][Discuss ...
- Educational Codeforces Round 41 967 E. Tufurama (CDQ分治 求 二维点数)
Educational Codeforces Round 41 (Rated for Div. 2) E. Tufurama (CDQ分治 求 二维点数) time limit per test 2 ...
- CDQ分治 陌上花开(三维偏序)
CDQ分治或树套树可以切掉 CDQ框架: 先分 计算左边对右边的贡献 再和 所以这个题可以一维排序,二维CDQ,三维树状数组统计 CDQ代码 # include <stdio.h> # i ...
- Codeforces 848C Goodbye Souvenir [CDQ分治,二维数点]
洛谷 Codeforces 这题我写了四种做法-- 思路 不管做法怎样,思路都是一样的. 好吧,其实不一样,有细微的差别. 第一种 考虑位置\(x\)对区间\([l,r]\)有\(\pm x\)的贡献 ...
- BOI2007 Mokia | cdq分治求二维点数模板
题目链接:戳我 也没什么,其实主要就是为了存一个求二维坐标上矩形内点的个数的模板.为了之后咕咕咕地复习使用 不过需要注意的一点是,树状数组传x的时候可千万不要传0了!要不然会一直死循环的...qwqw ...
- BZOJ2244: [SDOI2011]拦截导弹(CDQ分治,二维LIS,计数)
Description 某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统.但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度.并且能够拦截任意速度的导弹,但是以后每一发炮弹都不能高 ...
- BZOJ2225: [Spoj 2371]Another Longest Increasing CDQ分治,3维LIS
Code: #include <cstdio> #include <algorithm> #include <cstring> #define maxn 20000 ...
- CDQ分治学习笔记(三维偏序题解)
首先肯定是要膜拜CDQ大佬的. 题目背景 这是一道模板题 可以使用bitset,CDQ分治,K-DTree等方式解决. 题目描述 有 nn 个元素,第 ii 个元素有 a_iai.b_ibi.c_ ...
- CDQ分治学习笔记
数据结构中的一块内容:$CDQ$分治算法. $CDQ$显然是一个人的名字,陈丹琪(NOI2008金牌女选手) 这种离线分治算法被算法界称为"cdq分治" 我们知道,一个动态的问题一 ...
随机推荐
- mysql 多条数据中,分组获取值最大的数据记录
摘要: 多条纪录中,几个字段相同,但是其中一个或者多个字段不同,则去该字段最大(这里只有一个不同) 源数据: 目的是移除:在同一天中只能存在一天数据,则取审核日期最大,数据库脚本如下: SELECT ...
- MP构造器
官方文档:https://baomidou.gitee.io/mybatis-plus-doc/#/wrapper 实体包装器,用于处理 sql 拼接,排序,实体参数查询等! 补充说明: 使用的是数据 ...
- 建议各位亲使用LocalDateTime而不使用Date哦
在项目开发过程中经常遇到时间处理,但是你真的用对了吗,理解阿里巴巴开发手册中禁用static修饰SimpleDateFormat吗 通过阅读本篇文章你将了解到: 为什么需要LocalDate.Loca ...
- Django 学习笔记之模型高级用法
目录 1 复杂的字段类型 1.1 整数类型的区别 1.2 自增类型的区别 1.3 时间类型 1.4 FilePathField 1.5 FileField 1.6 ImageField 2 关系字段 ...
- SpringBoot 定时任务篇
一. 基于注解@Scheduled默认为单线程,开启多个任务时,任务的执行时机会受上一个任务执行时间的影响. 1.创建定时器 使用SpringBoot基于注解来创建定时任务非常简单,只需几行代码便可完 ...
- Linux下安装Nexus-3.15私服
1.切换到root用户 创建nexus用户:useradd nexus 设置nexus用户密码:passwd nexus 2.创建文件夹并进入该目录 linux中,软件包一般放在opt目录下,安装时, ...
- centos7删除PHP怎么操作
前面我们说了centos7删除MariaDB,现在我们说说centos7删除PHP怎么操作?当然不是特殊需要,不要去删除PHP,后果很严重.操作之前请做好所有的备份!首先查看有没安装php以及版本 # ...
- VS中的Modules窗口
当我在别人的机器上调试问题时,我做的第一件事就是查看modules窗口.按版本排序并看到一个不属于的dll可以帮助立即诊断配置问题,并节省许多调试痛苦. 下面介绍下各列的意思: Name:模块名称. ...
- MySQL 五大引擎之间的区别和优劣之分
MySQL五大引擎介绍: MYSQL支持三个引擎:ISAM.MYISAM和HEAP.另外两种类型INNODB和BERKLEY(BDB) ISAM:ISAM是一个定义明确且历经时间考验的数据表格管理方 ...
- Http 状态码(Status Codes)
七.状态码(Status Codes)*服务器向用户返回的状态码和提示信息,常见的有以下一些(方括号中是该状态码对应的HTTP动词).200 OK - [GET]:服务器成功返回用户请求的数据,该操作 ...