CDQ分治 & 整体分治
Part 1:CDQ分治
可以把CDQ分治理解为类似与归并排序求逆序对个数的一种分治算法(至少我现在是这么想的)。先处理完左右两边各自对答案的贡献,在处理跨越左右两边的对答案的贡献。
例题:
逆序对(二维偏序)
过水,不讲。
三维偏序
第一维先sort,第二维由归并保证,第三维在归并时查询权值树状数组。
\(Code:\)
int n, k, tot;
struct node{
int a, b, c, w, id;
}p[N], tp[N];
int ans[N];
ll tre[NN];
inline void add(int cur, int ad) {
for (register int i = cur; i <= k; i += lowbit(i)) {
tre[i] += ad;
}
}
inline ll query(int cur) {
ll res = 0;
for (register int i = cur; i; i -= lowbit(i)) {
res += tre[i];
}
return res;
}
bool cmp(const node &a, const node &b) {
if (a.a == b.a) {
if (a.b == b.b) {
return a.c < b.c;
} else {
return a.b < b.b;
}
}
return a.a < b.a;
}
inline bool comp(const node &a, const node &b) {
return a.a == b.a && a.b == b.b && a.c == b.c;
}
void cdq(int l, int r) {
if (l == r) {
ans[p[l].id] += p[l].w;
return ;
}
int mid = (l + r) >> 1;
cdq(l, mid); cdq(mid + 1, r);
int i = l, j = mid + 1, top = l - 1;
while (i <= mid && j <= r) {
if (p[i].b <= p[j].b) {
tp[++top] = p[i];
add(p[i].c, p[i].w);
++i;
} else {
ans[p[j].id] += query(p[j].c);
tp[++top] = p[j];
++j;
}
}
while (i <= mid) {
tp[++top] = p[i];
add(p[i].c, p[i].w);
++i;
}
while (j <= r) {
ans[p[j].id] += query(p[j].c);
tp[++top] = p[j];
++j;
}
for (register int i = l; i <= mid; ++i) {
add(p[i].c, -p[i].w);
}
for (register int i = l; i <= r; ++i) {
p[i] = tp[i];
}
}
ll bin[N];
int main() {
read(n); read(k);
int aa, bb, cc;
for (register int i = 1; i <= n; ++i) {
read(aa); read(bb); read(cc);
p[i + 1] = (node){aa, bb, cc, 1, i};
}
sort(p + 2, p + 2 + n, cmp);
for (register int i = 1; i <= n; ++i) {//去重
if (!comp(p[i], p[i + 1])) {
p[++tot] = p[i + 1];
} else {
p[tot].w++;
}
}
cdq(1, tot);
...
}
P3120 [USACO15FEB]Cow Hopscotch G
题意
现有递推式:
\]
\]
其中 \(n, m<=750,id[i][j] <= n * m\),求 \(f[1][1]\)。
题解
一看是三维偏序,我们就应该能想到CDQ分治
\(id[u][v] \not= id[i][j]\) 可以拆成严格大于和严格小于,然后就是俩三维偏序问题了。把对行数的分治套在外面,列直接暴力。我们从右往左枚举每一列,这样枚举到上面的时候,其右下部分都已经被统计。 实际上,我们还有更好的方法,不用拆 \(\not =\),放弃树状数组,能砍掉一个 \(log\)。具体方法是:我们可以直接维护当前 \(f\) 的总和 \(Tot\),以及其中每一个颜色编号的总和 \(sum[id]\)。由于 \(id\) 比较稀疏,我们无法每次都清空 \(sum\),因此我们记一个 \(T\),表示记录信息的时间,如果发现时间与当前不符的话,就清空重记。
然而,本题还和普通CDQ分治不同,我们原先并不知道每个位置的值,只有我们查询完以后才知道。因此我们可以采用分治FFT的方法,先递归下面,然后算下对上的贡献,最后递归上面。
关键代码:
inline void sol(int U, int D) {
if (U == D) return ;
int mid = (U + D) >> 1;
sol(mid + 1, D);
++nwtime;
int Tot = 0;
for (register int j = m; j; --j) {
for (register int i = U; i <= mid; ++i) {
if (T[h[i][j]] != nwtime) { T[h[i][j]] = nwtime, sum[h[i][j]] = 0; }
f[i][j] = f[i][j] + Tot - sum[h[i][j]];
if (f[i][j] >= P) f[i][j] -= P;
if (f[i][j] < 0) f[i][j] += P;
}
for (register int i = mid + 1; i <= D; ++i) {
if (T[h[i][j]] != nwtime) { T[h[i][j]] = nwtime, sum[h[i][j]] = 0; }
ADD(Tot, f[i][j]);
ADD(sum[h[i][j]], f[i][j]);
}
}
sol(U, mid);
}
习题
- 提示:三维为标号顺序,时间,大小关系
- 提示:最值树状数组;三维为时间,横坐标,纵坐标
Part 2:整体分治
如果说 CDQ 分治是基于时间的分治算法,那么整体分治实际上就是基于值域的整体分治算法。
具体讲解lyd书上已经讲得很不错了。这里说一下我的看法:
整体分治的一个经典应用为带(单点)修改的同时查询区间第k大。我们把操作离线下来,就变成了“支持单点修改的区间第k大”。由于要保证时间的关系,因此不能将其它的重新排序来搞事情。
CDQ 分治的做法是二分时间,递归子问题,然后右面累加上左面的贡献。整体分治可以理解为:二分值域,先搞定左面对右面的贡献,然后一块递归子问题去求解。类似权值线段树求第k大,“搞定左对右的贡献”的方法就是 \(k -= tmp\),其中 \(tmp\) 为小于 \(mid\) 的元素个数,这个要用树状数组查询。由于是暂仅考虑左面(小者)对右面(大者)的贡献,因此树状数组维护的是比 \(mid\) 小的位置的权值树状数组。感觉越说越乱
细节还是看代码吧
代码较丑,说一下易错点:
数组大小要开三倍!!(因为每个查询操作可能会带来俩操作)
注意真的修改 \(a\) 数组的值!!
例题:
P2617 Dynamic Rankings:动态区间第 \(k\) 大
题意同上。
做法:1.离线 2.对值域二分,将离线下来的一系列操作按照值域分成两组,递归解决
struct operations {
int type, x, y, z, id;
//两种可能。type=1/-1:x(值)y(useless)k(useless)id(位置) (add/delete)
//type = 0: x(left)y(right)k(k)id(位置) (query)
}opts[N], ql[N], qr[N];
void sol(int L, int R, int st, int ed) {
if (st > ed) return ;
if (L == R) {
for (register int i = st; i <= ed; ++i)
if (opts[i].type == 0)
ans[opts[i].id] = L;
return ;
}
int ltop = 0, rtop = 0, mid = (L + R) >> 1;
for (register int i = st; i <= ed; ++i) {
if (opts[i].type == 0) {
int tmp = q(opts[i].y) - q(opts[i].x - 1);
if (opts[i].k <= tmp) ql[++ltop] = opts[i];
else opts[i].k -= tmp, qr[++rtop] = opts[i];
} else {
if (opts[i].x <= mid) ad(opts[i].id, opts[i].type), ql[++ltop] = opts[i];
else qr[++rtop] = opts[i];
}
}
for (register int i = st; i <= ed; ++i)
if (opts[i].type != 0)
tre_clear(opts[i].id);
for (register int i = 1; i <= ltop; ++i)
opts[st - 1 + i] = ql[i];
for (register int i = 1; i <= rtop; ++i)
opts[st - 1 + ltop + i] = qr[i];
sol(L, mid, st, st + ltop - 1);
sol(mid + 1, R, st + ltop, ed);
}
...
//main()
for (register int i = 1; i <= n; ++i) {
read(a[i]);
opts[++otot] = (operations){1, a[i], 0, 0, i};
}
char opt[10];
for (register int i = 1; i <= m; ++i) {
scanf("%s", opt);
if (opt[0] == 'Q') {
read(aa); read(bb); read(cc);
opts[++otot] = (operations){0, aa, bb, cc, ++atot};
} else {
read(aa); read(bb);
opts[++otot] = (operations){-1, a[aa], 0, 0, aa};
a[aa] = bb;
opts[++otot] = (operations){1, a[aa], 0, 0, aa};
}
}
sol(1, ltot, 1, otot);
P3527 [POI2011]MET-Meteors:整体分治算法的优化
题意:维护长为 \(n\) 的带颜色序列,支持 \(m\) 次区间加(非负数)。最后查询每种颜色最早在什么时候权值总和达到了 \(p_i\)。
\(1 <= n, m <= 3e5\)
整体二分的扩展应用。
考虑到整体分治算法实际上是对二分算法的一个优化,相当于把一堆二分任务一块完成,因此我们最好先想出二分暴力 \(O(nmlogn)\) 的算法,再想法优化到 \(O(nlog^2n)\)。
二分的算法不难想出,因而整体分治的算法也不难想出。一种简便的处理 "NIE" 的方法为将总时间设置为 \(m + 1\),且第 \(m + 1\) 次设置为全部加正无穷,这样答案为 \(m + 1\) 实际上就是 "NIE"。
随便敲了个整体分治,然而TLE了 (尽管后来卡常卡过去了) 。毕竟整体二分是 \(O(nlog^2n)\) 的,对于 \(3e5\) 的数据不是很好卡。
这里介绍一种优化方法:
考虑到每种“时间”唯一对应一步操作,如果我们把它们也当作真的“操作”,每次都加入删除的话,比较费时间。不妨我们动态地加删,用什么就留什么。
具体来说,就是维护指针 \(nwt\) 表示现在 \(nwt\) 及其之前的时间的操作都累加上了, \(nwt\) 以后的操作还没有累加。我们通过不停的调整 \(nwt\),使得树状数组里面存的是我们想要的那个时间区间(1~mid)。这样就可以省去清空树状数组的时间。(实际上就是动态清空)
值得注意的是,如果用这种优化方法的话,我们就不用再每次对真“操作”的 \(k\) 都减去 \(sum\) 了,因为我们的 \(sum\) 是真的所有小于 \(mid\) 的和,而不是区间内部小于 \(mid\) 的和。
\(Code:\)
int nwt;
void sol(int L, int R, int st, int ed) {
if (st > ed) return ;
if (L == R) {
for (register int i = st; i <= ed; ++i)
ans[opts[i].id] = L;
return ;
}
int ltop = 0, rtop = 0, mid = (L + R) >> 1;
while (nwt < mid) nwt++, change(nwt, c[nwt]);//<= mid : add
while (nwt > mid) change(nwt, -c[nwt]), nwt--;//> mid : delete
for (register int i = st; i <= ed; ++i) {
ll sum = 0;
int id = opts[i].id;
for (register int j = 0; j < stat[id].size(); ++j) {
sum += q(stat[id][j]);
if (sum >= opts[i].x) {
sum = opts[i].x;
break;
}
}
if (opts[i].x <= sum) ql[++ltop] = opts[i];
else qr[++rtop] = opts[i];
}
for (register int i = 1; i <= ltop; ++i)
opts[st - 1 + i] = ql[i];
for (register int i = 1; i <= rtop; ++i)
opts[st - 1 + ltop + i] = qr[i];
sol(L, mid, st, st + ltop - 1);
sol(mid + 1, R, st + ltop, ed);
}
P1527 [国家集训队]矩阵乘法:二维数组的整体分治算法
题意:矩阵中询问子矩阵的第 k 大。
除了树状数组改为二维以外,无特殊的地方。同普通的整体分治求第k大。
介绍一种错误算法:
inline void tre_clear(int cur_x, int cur_y) {
for (register int i = cur_x; i <= n; i += lowbit(i))
for (register int j = cur_y; j <= n; j += lowbit(j)) {
//if (!tre[i][j]) return ;
tre[i][j] = 0;
}
}
将注释掉的代码加上将导致WA。但是不知道原因。可能原因比较复杂,尽量避免就好。
\(Code:\)my record
CDQ分治 & 整体分治的更多相关文章
- CDQ分治&整体二分学习个人小结
目录 小结 CDQ分治 二维LIS 第一道裸题 bzoj1176 Mokia bzoj3262 陌上花开 bzoj 1790 矩形藏宝地 hdu5126四维偏序 P3157 [CQOI2011]动态逆 ...
- CQD(陈丹琦)分治 & 整体二分——专题小结
整体二分和CDQ分治 有一些问题很多时间都坑在斜率和凸壳上了么--感觉斜率和凸壳各种搞不懂-- 整体二分 整体二分的资料好像不是很多,我在网上找到了一篇不错的资料: 整体二分是个很神的东西 ...
- Codeforces 161.D. Distance in Tree-树分治(点分治,不容斥版)-树上距离为K的点对数量-蜜汁TLE (VK Cup 2012 Round 1)
D. Distance in Tree time limit per test 3 seconds memory limit per test 512 megabytes input standard ...
- 洛谷 P3806 【模板】点分治1-树分治(点分治,容斥版) 模板题-树上距离为k的点对是否存在
P3806 [模板]点分治1 题目背景 感谢hzwer的点分治互测. 题目描述 给定一棵有n个点的树 询问树上距离为k的点对是否存在. 输入格式 n,m 接下来n-1条边a,b,c描述a到b有一条长度 ...
- POJ 1741.Tree and 洛谷 P4178 Tree-树分治(点分治,容斥版) +二分 模板题-区间点对最短距离<=K的点对数量
POJ 1741. Tree Time Limit: 1000MS Memory Limit: 30000K Total Submissions: 34141 Accepted: 11420 ...
- 【题解】P4755 Beautiful Pair(启发式合并的思路+分治=启发式分治)
[题解]P4755 Beautiful Pair upd: 之前一个first second烦了,现在AC了 由于之前是直接抄std写的,所以没有什么心得体会,今天自己写写发现 不知道为啥\(90\) ...
- 一篇自己都看不懂的CDQ分治&整体二分学习笔记
作为一个永不咕咕咕的博主,我来更笔记辣qaq CDQ分治 CDQ分治的思想还是比较简单的.它的基本流程是: \(1.\)将所有修改操作和查询操作按照时间顺序并在一起,形成一段序列.显然,会影响查询操作 ...
- Cdq分治整体二分学习记录
这点东西前前后后拖了好几个星期才学会……还是自己太菜啊. Cdq分治的思想是:把问题序列分割成左右两个,先单独处理左边,再处理左边对右边的影响,再单独处理右边.这样可以消去数据结构上的一个log,降低 ...
- [学习笔记] CDQ分治&整体二分
突然诈尸.png 这两个东西好像都是离线骗分大法... 不过其实这两个东西并不是一样的... 虽然代码长得比较像 CDQ分治 基本思想 其实CDQ分治的基本思想挺简单的... 大概思路就是长这样的: ...
随机推荐
- 尚硅谷 dubbo学习视频
1 1.搭建zookpeer注册中心 windows下载zooker 需要修改下zoo_sample .cfg为zoo.cnf 然后需要zoo.cnf中数据文件的路径 第五步:把zoo_sample ...
- netty解决TCP的拆包和粘包的解决办法
TCP粘包.拆包问题 熟悉tcp编程的可能知道,无论是服务端还是客户端,当我们读取或者发送数据的时候,都需要考虑TCP底层的粘包个拆包机制. tcp是一个“流”协议,所谓流就是没有界限的传输数据,在业 ...
- 【服务器】CentOs7系统使用宝塔面板搭建网站,有FTP配置(保姆式教程)
内容繁多,请耐心跟着流程走,在过程中遇到问题请在下面留言(我只是小白,请专业人士喷轻点). 这次用thinkphp5.1做演示,单纯的做演示,我打算下一篇文章用typecho(博客框架)演示. 前言 ...
- 学习python的基本了解
1) 使用python打印信息,分别打印你的名字.年龄.爱好# print('wang,23,shopping')# 2)使用变量,分别打印你的名字.年龄.爱好# name='wang'# age=2 ...
- Python 简明教程 --- 8,Python 字符串函数
微信公众号:码农充电站pro 个人主页:https://codeshellme.github.io 好代码本身就是最好的文档.当你需要添加一个注释时,你应该考虑如何修改代码才能不需要注释. -- St ...
- maven在idea中的配置的注意点
1.基本的配置查看尚硅谷的文档链接在下 链接:https://pan.baidu.com/s/18gwll6gU38qNH2P01To-lQ 提取码:oq40 2.注意点: 需要将新建项目的配置也修改 ...
- 入门大数据---Spark_Streaming基本操作
一.案例引入 这里先引入一个基本的案例来演示流的创建:获取指定端口上的数据并进行词频统计.项目依赖和代码实现如下: <dependency> <groupId>org.apac ...
- jquery入门(3)
4.jQuery中的事件绑定 4.1.事件绑定 on方法绑定 $('#box').on('click',function(){ alert(1); }) 直接绑定 $("#box" ...
- Nginx配置upstream并且实现负载均衡
感谢看过这一些列博文和评论的小伙伴, 我把自己所看到的学到的拿到这里来分享是想和大家一起学习进步, 想听听园友给出的意见, 也是对自己学习过程的一个总结. 技术无止境, 我们仍需努力! 1,话不多说, ...
- [区间+线性dp]数字游戏
题目描述 丁丁最近沉迷于一个数字游戏之中.这个游戏看似简单,但丁丁在研究了许多天之后却发觉原来在简单的规则下想要赢得这个游戏并不那么容易.游戏是这样的,在你面前有一圈整数(一共\(n\)个),你要按顺 ...