<更新提示>

<第一次更新>

<第二次更新>更新了三维偏序问题的拓展


<正文>

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分治和多维偏序问题』的更多相关文章

  1. 【BZOJ-3262】陌上花开 CDQ分治(3维偏序)

    3262: 陌上花开 Time Limit: 20 Sec  Memory Limit: 256 MBSubmit: 1439  Solved: 648[Submit][Status][Discuss ...

  2. Educational Codeforces Round 41 967 E. Tufurama (CDQ分治 求 二维点数)

    Educational Codeforces Round 41 (Rated for Div. 2) E. Tufurama (CDQ分治 求 二维点数) time limit per test 2 ...

  3. CDQ分治 陌上花开(三维偏序)

    CDQ分治或树套树可以切掉 CDQ框架: 先分 计算左边对右边的贡献 再和 所以这个题可以一维排序,二维CDQ,三维树状数组统计 CDQ代码 # include <stdio.h> # i ...

  4. Codeforces 848C Goodbye Souvenir [CDQ分治,二维数点]

    洛谷 Codeforces 这题我写了四种做法-- 思路 不管做法怎样,思路都是一样的. 好吧,其实不一样,有细微的差别. 第一种 考虑位置\(x\)对区间\([l,r]\)有\(\pm x\)的贡献 ...

  5. BOI2007 Mokia | cdq分治求二维点数模板

    题目链接:戳我 也没什么,其实主要就是为了存一个求二维坐标上矩形内点的个数的模板.为了之后咕咕咕地复习使用 不过需要注意的一点是,树状数组传x的时候可千万不要传0了!要不然会一直死循环的...qwqw ...

  6. BZOJ2244: [SDOI2011]拦截导弹(CDQ分治,二维LIS,计数)

    Description 某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统.但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度.并且能够拦截任意速度的导弹,但是以后每一发炮弹都不能高 ...

  7. BZOJ2225: [Spoj 2371]Another Longest Increasing CDQ分治,3维LIS

    Code: #include <cstdio> #include <algorithm> #include <cstring> #define maxn 20000 ...

  8. CDQ分治学习笔记(三维偏序题解)

    首先肯定是要膜拜CDQ大佬的. 题目背景 这是一道模板题 可以使用bitset,CDQ分治,K-DTree等方式解决. 题目描述 有 nn 个元素,第 ii 个元素有 a_iai​.b_ibi​.c_ ...

  9. CDQ分治学习笔记

    数据结构中的一块内容:$CDQ$分治算法. $CDQ$显然是一个人的名字,陈丹琪(NOI2008金牌女选手) 这种离线分治算法被算法界称为"cdq分治" 我们知道,一个动态的问题一 ...

随机推荐

  1. 2019CISCN web题赛-JustSoSo;love_math(复现)

    0x00前言 这几天从网上找个CMS源码开始练习审计,盯着众多的代码debug调呀调头晕脑胀的,还不错找到个文件读取和一个ssrf... 上月底结束的CISCN线上赛,web四道,仔细研究的2道,做出 ...

  2. FPM 1.1正式版 Search & List

    前面写的FPM都是自己练习用的.直到自己正式用了一个,才发现一些小问题.feeder class写在一起和分开写有好有坏,这里就不说了. 自己做了个小的查询报表如下: 现在来按SAP官方的做法来重新做 ...

  3. PHP:CURL分别以GET、POST方式请求HTTPS协议接口api【转】

    1.curl以GET方式请求https协议接口 //注意:这里的$url已经包含参数了,不带参数你自己处理哦GET很简单 function curl_get_https($url){ $curl = ...

  4. Cron Expressions——Cron 表达式(QuartZ调度时间配置)

    如果你需要像日历那样按日程来触发任务,而不是像SimpleTrigger 那样每隔特定的间隔时间触发,CronTriggers通常比SimpleTrigger更有用. 使用CronTrigger,你可 ...

  5. MySQL的select多表查询

    select 语句: select 语句一般用法为: select 字段名 from tb_name where 条件 ; select 查询语句类型一般分为三种:  单表查询,多表查询,子查询 最简 ...

  6. AtCoder - 4496 G - k-DMC

    AtCoder - 4496 G - k-DMC 题目 长度为n的字符串,q次查询,问"DMC"(不要求连续)在字符串中出现的次数,其中D和M的距离不超过k. 错误思路 通过遍历字 ...

  7. angular-依赖注入 显示注入/隐式注入

    1.隐式注入:不需要开发人员干预,angularJS自动根据参数的名称识别和注入数据 app.controller("myCtrl".function($scope) { $sco ...

  8. Spring Boot版本号说明

    Spring Boot的版本选择一般是这样的,如下图: 那版本号后面的英文代表什么含义呢? 具体含义,如下文所示: SNAPSHOT:快照版,表示开发版本,随时可能修改: M1(Mn):M是miles ...

  9. html2canvas@^1.0.0-rc.1

    这个版本的html2canvas是我在npm找到的,有严重问题,如截图后字体变小,解决方法就是官网找个min.js的版本,放到项目中引用就好 https://html2canvas.hertzen.c ...

  10. django -- 模版语言之过滤器Filters和for循环

    前戏 在前面写的图书管理系统中,我们对模版语言应该已经不陌生了,使用{{ }}包裹起来的就是模版语言,只需要记住两种就可以了 {{ 变量名 }}            变量相关的 {% %}      ...