离线分治算法——CDQ分治详解

本文介绍了离线算法CDQ分治,通过两种方式——基于时间的分治和基于值域的整体分治,解决区间查询和修改的问题。通过实例详细解析算法思想和实现,包括如何利用树状数组和分治策略优化复杂度至O(nlog2n)。
摘要由CSDN通过智能技术生成

以前做过的许多题目,都是在线算法,也就是对与一系列的询问,利用某种数据结构逐个求出答案。

而今天学了一种离线算法——CDQ分治,是将全部的询问放在一起,利用分治一同处理。

CDQ分治,由2008年国际信息学奥林匹克竞赛(IOI)金牌女选手陈丹琦在国家集训队中引入而得名,为算法竞赛界中的一个广泛称呼。

CDQ分治有两种,分别是基于时间的分治基于值域的整体分治

下面看就开始吧。


1.基于时间的分治

BZOJ2716–天使玩具
【题意】
Ayu在七年前曾经收到过一个天使玩偶,当时她把它当做时间囊埋在了地下。
而七年后的今天,Ayu却忘了她把天使玩偶埋在了哪里,所以她决定仅凭一点模糊的记忆来寻找它。
我们把Ayu生活的小镇看做一个二维平面直角坐标系,而Ayu会不定时的记起可能在某个点(x,y)埋下了天使玩偶。
或者Ayu会询问你,假如她在(x,y),那么她离最近的天使玩偶可能埋下的地方有多远。
因为Ayu只会沿着平行坐标轴的方向来行动,所以在这个问题里我们定义两个点之间的距离为曼哈顿距离:
dist(A,B)=|Ax−Bx|+|Ay−By|
其中Ax,Ay表示点A的横坐标,其余类似。
【输入格式】
第一行包含两个整数n和m,在刚开始时,Ayu已经知道有n个点可能埋着天使玩偶,接下来Ayu要进行m次操作。
接下来n行,每行两个非负整数xi,yi,表示初始n个点的坐标。
再接下来m行,每行三个非负整数 t,x,y 。
如果t=1,表示Ayu又回忆起了一个可能埋着玩偶的点(x,y)。
如果t=2,表示Ayu询问如果她在坐标(x,y),那么在已经回忆出的点里,离她最近的那个点有多远。
【输出格式】
对于每个t=2的询问,在单独的一行内输出该询问的结果。
【数据范围】
n , m ≤ 5 ∗ 1 0 5 n,m≤5∗10^5 n,m5105,坐标范围为 0 − 1 0 6 0 -10^6 0106
【输入样例】
2 3
1 1
2 3
2 1 2
1 3 3
2 4 2
【输出样例】
1
2

我们一般先解决该问题的简化版——假设没有t=1的操作。这时,平面上有n个点 ( x i , y i ) (x_i,y_i) (xi,yi),然后询问与 ( x , y ) (x,y) (x,y)最近的点有多远,答案为:
min(abs(x-xi)+abs(y-yi))
于是我们考虑将绝对值拆开,分为左上、右上、左下、右下四个位置
对于左下方为例,此时要求的式子变为:
min(x-xi+y-yi)
化简得到:
(x+y)-max(xi+yi)
然后就好办了,我们先将每一个坐标按x为第一关键字,y为第二关键字排序。然后用树状数组来维护[0,y]从左到右不断维护就可以了。
对于其他三个方向,做法也是如此。

然后我们加入修改操作。我们先将插入和询问合并在一起,然后进行分治。为什么要分治呢,举个例子:
在这里插入图片描述
在这个图中,我们假设橙色是查询,绿色是修改
然后我们就对其他进行分治
在这里插入图片描述
先是把它分成1-4和5-8,用树状数组记录1-4中的修改,给5-8的询问加上这些修改。
然后再分(拿1-4为例子),分成1-2和3-4,用树状数组记录,给3-4中的询问加上
后面同理。
总的来讲,就是把当前的分成两份,用树状数组记录1-mid里面的修改操作,然后给mid+1-r里面的询问操作加上这些结果
因此,我们刚好使每个询问都经过了全部的操作,时间复杂度 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)

参考代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cstdlib>
using namespace std;
const int u = 1000010;
struct rec {
    int x, y, z; };
rec a[u]; // 原始问题的操作序列(长度为n+m)
rec b[u]; // 静态问题的坐标(按横坐标排序)及其在a中的下标
int c[u], tot; // 树状数组、坐标的最大范围
int ans[u], n, m, t;
 
bool operator <(const rec &a, const rec &b) {
   
    return a.x < b.x || a.x == b.x && a.y < b.y;
}
 
int ask(int x) {
   
    int y = -(1 << 30);
    for (; x; x -
  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值