CDQ分治-陌上花开

题目大意

对于给遗传给定的序列:

\[(x,y,z)_1, (x,y,z)_2, (x,y,z)_3, \cdots, (x,y,z)_n
\]

求:

\[\sum_{x_i < x_j,~y_i < y_j,~z_i<z_j,~i ≠j}1
\]

题解:

CDQ分治,顾名思义就是要进行分治,但是它可以解决比普通分治更多的问题。CDQ分治的整体思想,是:

  1. 对于一个需要解决问题的区间\([l,r)\),将其一分为二,变为\([l,mid),[mid,r)\)。
  2. 对于这两个被分开的区间,分别进行递归解决。
  3. 完成递归解决之后,计算左边对右边的贡献(有的时候可能要计算右边对左边的计算或者互相都要算)
  4. 完成,统计答案。

在这道题当中,需要解决的是三维偏序的问题,而首先想到的是使用二维树状数组进行求逆序对,将这三维当中的两维当作下标,对最后一维求逆序对。但是这种做法显而易见,空间会爆掉,是开不下的。所以我们需要采取某种算法或者数据结构来代替树状数组的这意味空间。

在这里,就是用了CDQ分治。

首先解决第一维的问题:以第一维作为第一关键字,第二维作为第二关键字,第三维作为第三关键字,进行排序。比较函数如下:

bool cmp1(Point a, Point b) {
if (a.x == b.x && a.y == b.y) return a.z < b.z;
if (a.x == b.x) return a.y < b.y;
return a.x < b.x;
}

在进行排序之后,就保证了整个序列单调不下降。

随后进行CDQ分治:solve(0,n)

  1. solve(0,mid)solve(mid, n)

  2. 计算左边对右边的影响(原因:整个序列对于第一维变量单调不下降,所以右边的大数无法对小数产生答案上的贡献)

    • 对\([l,mid),[mid,r)\)分别进行以第二维作为第一关键字,第三维作为第二关键字的排序:

      bool cmp2(Point a, Point b) {
      if (a.y == b.y) return a.z < b.z;
      return a.y < b.y;
      }

      注意,这里是直接对原数组进行排序,而且实际上不会产生任何问题,因为被排序的两个子序列的这两个子问题,共同属于同一个问题,所以在进行其父亲问题的解决时还会进行重拍,届时并不会产生元素跨界问题。

    • 进行双指针扫描,双指针扫描的目的就是直接使用树状数组对每个元素进行答案统计。

整个问题解决完毕。

但是其存在一定的问题:由于CDQ分治这里我们讨论过了,应该只需要计算左边对右边的影响,但是忽略了一种情况,就是两个元素完全相同,那么左右应该共享影响,但是这里是非常难做到的,所以对于输入的时候,直接进行去重操作,并且赋予每个元素重复次数的权值,方便计算。

注意:在统计答案的时候,会看到下式:

ans[A[i].ans + A[i].cnt - 1] += A[i].cnt

这里还要加上A[i].cnt-1的原因其实也不难想到,就是去重的逆运算。

历届悲惨代码如下:

第一次(WA,0分,没有去重):

#include <iostream>
#include <algorithm> using namespace std; typedef long long ll;
typedef pair<ll, pair<ll, ll> > Point; const ll maxn = 200005; Point A[maxn];
ll n, K, ord[maxn], f[maxn], T[maxn], ans[maxn]; bool cmp(ll x, ll y) {
return A[x].second < A[y].second;
} ll lb(ll i) { return i & (-i); } void add(ll i, ll delta) {
while (i <= K) {
T[i] += delta;
i += lb(i);
}
} ll sum(ll i) {
ll ans = 0;
while (i > 0) {
ans += T[i];
i -= lb(i);
}
return ans;
} // 左闭右开
void solve(ll left, ll right) {
if (right - left == 1) return;
ll mid = (right + left) / 2;
solve(left, mid); solve(mid, right);
for (ll i = left; i < right; i ++)
ord[i] = i;
sort(ord + left, ord + right, cmp);
// 左边对右边的理想
for (ll i = left; i < right; i ++) {
ll k = ord[i];
if (k < mid) add(A[k].second.second, 1);
else f[k] += sum(A[k].second.second);
}
for (ll i = left; i < right; i ++)
if (ord[i] < mid)
add(A[ord[i]].second.second, -1);
} int main() {
// input;
cin >> n >> K;
for (ll i = 0; i < n; i ++)
cin >> A[i].first >> A[i].second.first >> A[i].second.second;
sort(A, A + n);
solve(0, n);
for (ll i = 0; i < n; i ++)
ans[f[i]] ++;
for (ll i = 0; i < n; i ++)
cout << ans[i] << endl;
return 0;
}

第二次(WA,0分,去重了,答案统计没做逆运算):

#include <iostream>
#include <algorithm>
#define inf 10000000 using namespace std; typedef long long ll;
struct Point {
ll x, y, z, cnt;
friend bool operator == (const Point& a, const Point& b) {
return (a.x == b.x) && (a.y == b.y) && (a.z == b.z);
}
friend bool operator < (const Point& a, const Point& b) {
if (a.x == b.x && a.y == b.y && a.z == b.z) return a.cnt < b.cnt;
if (a.x == b.x && a.y == b.y) return a.z < b.z;
if (a.x == b.x) return a.y < b.y;
return a.x < b.x;
}
}; const ll maxn = 200005; Point A[maxn];
ll n, K, ord[maxn], f[maxn], T[maxn], ans[maxn]; inline bool cmp(ll x, ll y) {
if (A[x].y == A[y].y) return A[x].z < A[y].z;
return A[x].y < A[y].y;
} inline ll lb(ll i) { return i & (-i); } inline void add(ll i, ll delta) {
while (i <= K) {
T[i] += delta;
i += lb(i);
}
} inline ll sum(ll i) {
ll ans = 0;
while (i > 0) {
ans += T[i];
i -= lb(i);
}
return ans;
} // 左闭右开
void solve(ll left, ll right) {
if (right - left == 1) return;
ll mid = (right + left) / 2;
solve(left, mid); solve(mid, right);
for (ll i = left; i < right; i ++)
ord[i] = i;
sort(ord + left, ord + right, cmp);
// 左边对右边的贡献
for (ll i = left; i < right; i ++) {
ll k = ord[i];
if (k < mid) add(A[k].z, A[k].cnt);
else f[k] += sum(A[k].z);
}
for (ll i = left; i < right; i ++) {
ll k = ord[i];
if (k < mid)
add(A[k].z, (-1 * A[k].cnt));
}
} int main() {
// input;
cin >> n >> K;
for (ll i = 0; i < n; i ++) {
cin >> A[i].x >> A[i].y >> A[i].z;
A[i].cnt = 1;
} sort(A, A + n); ll multiple_count = 0;
for (ll i = 1; i < n; i ++)
if (A[i] == A[i - 1]) {
A[i].cnt = A[i - 1].cnt + 1;
multiple_count ++;
A[i - 1] = (Point) {inf, inf, inf, inf};
}
sort(A, A + n);
cout << "-----------------" << endl;
for (int i = 0; i < n - multiple_count; i ++)
cout << A[i].x << " " << A[i].y << " " << A[i].z << " " << A[i].cnt << " " << endl;
solve(0, n - multiple_count);
for (ll i = 0; i < n - multiple_count; i ++)
ans[f[i]] += A[i].cnt;
for (ll i = 0; i < n; i ++)
cout << ans[i] << endl;
return 0;
}

第三次(WA,10分,不知道哪里错了):

#include <iostream>
#include <algorithm>
#define inf 10000000 using namespace std; typedef long long ll;
struct Point {
ll x, y, z, cnt;
friend bool operator == (const Point& a, const Point& b) {
return (a.x == b.x) && (a.y == b.y) && (a.z == b.z);
}
friend bool operator < (const Point& a, const Point& b) {
if (a.x == b.x && a.y == b.y && a.z == b.z) return a.cnt < b.cnt;
if (a.x == b.x && a.y == b.y) return a.z < b.z;
if (a.x == b.x) return a.y < b.y;
return a.x < b.x;
}
}; const ll maxn = 200005; Point A[maxn];
ll n, K, ord[maxn], f[maxn], T[maxn], ans[maxn]; inline bool cmp(ll x, ll y) {
if (A[x].y == A[y].y) return A[x].z < A[y].z;
return A[x].y < A[y].y;
} inline ll lb(ll i) { return i & (-i); } inline void add(ll i, ll delta) {
while (i <= K) {
T[i] += delta;
i += lb(i);
}
} inline ll sum(ll i) {
ll ans = 0;
while (i > 0) {
ans += T[i];
i -= lb(i);
}
return ans;
} // 左闭右开
void solve(ll left, ll right) {
if (right - left == 1) return;
ll mid = (right + left) / 2;
solve(left, mid); solve(mid, right);
for (ll i = left; i < right; i ++)
ord[i] = i;
sort(ord + left, ord + right, cmp);
// 左边对右边的贡献
for (ll i = left; i < right; i ++) {
ll k = ord[i];
if (k < mid) add(A[k].z, A[k].cnt);
else f[k] += sum(A[k].z);
}
for (ll i = left; i < right; i ++) {
ll k = ord[i];
if (k < mid)
add(A[k].z, (-1 * A[k].cnt));
}
} int main() {
// input;
cin >> n >> K;
for (ll i = 0; i < n; i ++) {
cin >> A[i].x >> A[i].y >> A[i].z;
A[i].cnt = 1;
} sort(A, A + n); ll multiple_count = 0;
for (ll i = 1; i < n; i ++)
if (A[i] == A[i - 1]) {
A[i].cnt = A[i - 1].cnt + 1;
multiple_count ++;
A[i - 1] = (Point) {inf, inf, inf, inf};
}
sort(A, A + n);
cout << "-----------------" << endl;
for (int i = 0; i < n - multiple_count; i ++)
cout << A[i].x << " " << A[i].y << " " << A[i].z << " " << A[i].cnt << " " << endl;
solve(0, n - multiple_count);
for (ll i = 0; i < n - multiple_count; i ++) {
f[i] += A[i].cnt - 1;
}
for (ll i = 0; i < n - multiple_count; i ++)
ans[f[i]] += A[i].cnt;
for (ll i = 0; i < n; i ++)
cout << ans[i] << endl;
return 0;
}

第四次,放弃了,使用双指针扫描(AC)

#include <iostream>
#include <algorithm>
#define inf 1000000000 using namespace std; typedef long long ll;
struct Point {
ll x, y, z, cnt, ans;
friend bool operator == (const Point& a, const Point& b) {
return (a.x == b.x) && (a.y == b.y) && (a.z == b.z);
}
}; const int maxn = 300005; Point A[maxn];
ll n, K, ord[maxn], T[maxn], ans[maxn]; inline bool cmp2(Point a, Point b) {
if (a.y == b.y) return a.z < b.z;
return a.y < b.y;
} inline bool cmp1(Point a, Point b) {
if (a.x == b.x && a.y == b.y) return a.z < b.z;
if (a.x == b.x) return a.y < b.y;
return a.x < b.x;
} inline ll lb(ll i) { return i & (-i); } inline void add(ll i, ll delta) {
while (i <= K) {
T[i] += delta;
i += lb(i);
}
} inline ll sum(ll i) {
ll ans = 0;
while (i > 0) {
ans += T[i];
i -= lb(i);
}
return ans;
} // 左闭右开
void solve(ll left, ll right) {
if (right - left == 1) return;
ll mid = (right + left) >> 1;
solve(left, mid); solve(mid, right);
sort(A + left, A + mid, cmp2);
sort(A + mid, A + right, cmp2);
ll i, j = left;
for (i = mid; i < right; i ++) {
while (A[i].y >= A[j].y && j < mid) {
add(A[j].z, A[j].cnt); j ++;
}
A[i].ans += sum(A[i].z);
}
for (i = left; i < j; i ++)
add(A[i].z, -A[i].cnt);
} int main() {
// Input
cin >> n >> K;
for (ll i = 0; i < n; i ++) {
cin >> A[i].x >> A[i].y >> A[i].z;
A[i].cnt = 1;
}
// Unique
sort(A, A + n, cmp1); ll multiple_count = 0;
for (ll i = 1; i < n; i ++)
if (A[i] == A[i - 1]) {
A[i].cnt = A[i - 1].cnt + 1;
multiple_count ++;
A[i - 1] = (Point) {inf, inf, inf, inf};
}
sort(A, A + n, cmp1);
// cout << "-----------------" << endl;
// for (ll i = 0; i < n - multiple_count; i ++)
// cout << A[i].x << " " << A[i].y << " " << A[i].z << " " << A[i].cnt << " \t";
// cout << endl;
solve(0, n - multiple_count);
for (ll i = 0; i < n - multiple_count; i ++)
ans[A[i].ans + A[i].cnt - 1] += A[i].cnt;
for (ll i = 0; i < n; i ++)
cout << ans[i] << endl;
return 0;
}

第五次,“我是专业的”的著名金牌OI教练解决了该问题(树状数组)

原因:在排序的过程中,对于区间:

\(\text{left}:[1]: (1, 1, 1),[2]: (1,2,1),[3]:(3,1,3),[4]:(3,3,6);\\\text{right}:[5]:(1,2,1),[6]:(2,4,3),[7]:(3,1,3),[8]:(3,3,6)\)

在重新排序之后就会变为:

\([1]:(1,1,1), [5]:(1,2,1),[2]:(1,2,1),[6]:(2,4,3),[7]:(3,1,3),[3]:(3,1,3),[8]:(3,3,6),[4]:(3,3,6)\)

这种排列方式显然就是错误的因为\(2,5;~3,7;~4,8\)这三组数据位置颠倒了。而解决这个问题的方式就是更正排序方式:

  1. 将\(y\)作为第一排序关键字
  2. 将\(z\)作为第二排序关键字
  3. 将\(x\)作为第三排序关键字
#include <iostream>
#include <algorithm>
#define inf 1000000000 using namespace std; typedef long long ll;
struct Point {
ll x, y, z, cnt;
friend bool operator == (const Point& a, const Point& b) {
return (a.x == b.x) && (a.y == b.y) && (a.z == b.z);
}
}; const int maxn = 300005; Point A[maxn];
ll n, K, ord[maxn], f[maxn], T[maxn], ans[maxn]; inline bool cmp2(ll x, ll y) {
if (A[x].y == A[y].y && A[x].z == A[y].z) return A[x].x < A[y].x;
if (A[x].y == A[y].y) return A[x].z < A[y].z;
return A[x].y < A[y].y;
} inline bool cmp1(Point a, Point b) {
if (a.x == b.x && a.y == b.y) return a.z < b.z;
if (a.x == b.x) return a.y < b.y;
return a.x < b.x;
} inline ll lb(ll i) { return i & (-i); } inline void add(ll i, ll delta) {
while (i <= K) {
T[i] += delta;
i += lb(i);
}
} inline ll sum(ll i) {
ll ans = 0;
while (i > 0) {
ans += T[i];
i -= lb(i);
}
return ans;
} // 左闭右开
void solve(ll left, ll right) {
if (right - left == 1) return;
ll mid = (right + left) >> 1;
solve(left, mid); solve(mid, right);
// 根据y的值对数组进行排序,和求逆序对时的构造离散化类似
// 即在这里,y是求逆序对当中的时序
for (ll i = left; i < right; i ++)
ord[i] = i;
sort(ord + left, ord + right, cmp2);
// 左边对右边的贡献
for (ll i = left; i < right; i ++) {
ll k = ord[i];
if (k < mid) add(A[k].z, A[k].cnt);
else f[k] += sum(A[k].z);
}
// Clear Binary Index Tree
for (ll i = left; i < right; i ++) {
ll k = ord[i]; ord[i] = 0;
if (k < mid) add(A[k].z, (-1 * A[k].cnt));
}
} int main() {
// Input
cin >> n >> K;
for (ll i = 0; i < n; i ++) {
cin >> A[i].x >> A[i].y >> A[i].z;
A[i].cnt = 1;
}
// Unique
sort(A, A + n, cmp1); ll multiple_count = 0;
for (ll i = 1; i < n; i ++)
if (A[i] == A[i - 1]) {
A[i].cnt = A[i - 1].cnt + 1;
multiple_count ++;
A[i - 1] = (Point) {inf, inf, inf, inf};
}
sort(A, A + n, cmp1);
// cout << "-----------------" << endl;
// for (ll i = 0; i < n - multiple_count; i ++)
// cout << A[i].x << " " << A[i].y << " " << A[i].z << " " << A[i].cnt << " \t";
// cout << endl;
solve(0, n - multiple_count);
for (ll i = 0; i < n - multiple_count; i ++) {
f[i] += A[i].cnt - 1;
}
for (ll i = 0; i < n - multiple_count; i ++)
ans[f[i]] += A[i].cnt;
for (ll i = 0; i < n; i ++)
cout << ans[i] << endl;
return 0;
}

CDQ分治-陌上花开(附典型错误及原因)的更多相关文章

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

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

  2. cdq分治 陌上花开(内无题解)

    由于有归并排序 要注意是对原来的那个元素进行更新答案和删除操作 而不是占据原来那个元素下标的元素

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

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

  4. 【BZOJ3262】陌上花开(CDQ分治)

    [BZOJ3262]陌上花开(CDQ分治) 题解 原来放过这道题目,题面在这里 树套树的做法也请点上面 这回用CDQ分治做的 其实也很简单, 对于第一维排序之后 显然只有前面的对后面的才会产生贡献 那 ...

  5. bzoj3262陌上花开 cdq分治

    3262: 陌上花开 Time Limit: 20 Sec  Memory Limit: 256 MBSubmit: 2794  Solved: 1250[Submit][Status][Discus ...

  6. 洛谷P3810 陌上花开 CDQ分治(三维偏序)

    好,这是一道三维偏序的模板题 当然没那么简单..... 首先谴责洛谷一下:可怜的陌上花开的题面被无情的消灭了: 这么好听的名字#(滑稽) 那么我们看了题面后就发现:这就是一个三维偏序.只不过ans不加 ...

  7. Luogu 3810 & BZOJ 3262 陌上花开/三维偏序 | CDQ分治

    Luogu 3810 & BZOJ 3263 陌上花开/三维偏序 | CDQ分治 题面 \(n\)个元素,每个元素有三个值:\(a_i\), \(b_i\) 和 \(c_i\).定义一个元素的 ...

  8. 【BZOJ3262】陌上花开 cdq分治

    [BZOJ3262]陌上花开 Description 有n朵花,每朵花有三个属性:花形(s).颜色(c).气味(m),又三个整数表示.现要对每朵花评级,一朵花的级别是它拥有的美丽能超过的花的数量.定义 ...

  9. 【BZOJ 3262】 3262: 陌上花开 (CDQ分治)

    3262: 陌上花开 Description 有n朵花,每朵花有三个属性:花形(s).颜色(c).气味(m),又三个整数表示.现要对每朵花评级,一朵花的级别是它拥有的美丽能超过的花的数量.定义一朵花A ...

随机推荐

  1. webpack初学踩坑记

    注意事项: 1. webpack不用装在全局环境下,在哪个项目中使用,就安装在该项目下即可 1. 问题一:npm init 初始化一个项目后,添加webpack.config.js文件,在该项目中通过 ...

  2. pandas库笔记

    本笔记为自学笔记 1.pandas.DataFrame() 一种保存矩阵的数据格式 grades_df = pd.DataFrame( data={'exam1': [43, 81, 78, 75, ...

  3. 【转载】巴塞尔问题(Basel Problem)的多种解法

    如何计算 \(\displaystyle \zeta \left ( 2 \right )=\frac{1}{1^{2}}+\frac{1}{2^{2}}+\frac{1}{3^{2}}+\cdots ...

  4. 定时备份mysql数据库的shell脚本

    最近项目需要定时备份mysql数据库的数据,根据需求写了一份定时备份mysql数据库的脚本. -h mysql的地址  默认为localhost -P 端口号  默认为3306 -u 用户  默认为r ...

  5. java 限制每隔15分钟才允许执行一次程序

    由于公司订餐平台,有个用户催单业务,每当用户点击催单按钮时,商家就会收到消息提示,如果用户频繁的发起催单请求,这样商家就会不停的收到消息提醒,所以想限制用户至少每隔15分钟才可以催单一次 我采取了以下 ...

  6. JPA中实现双向多对多的关联关系(附代码下载)

    场景 JPA入门简介与搭建HelloWorld(附代码下载): https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/103473937 ...

  7. C语言笔记 11_头文件&强制类型转换&错误处理&递归

    头文件 头文件是扩展名为 .h 的文件,包含了 C 函数声明和宏定义,被多个源文件中引用共享.有两种类型的头文件:程序员编写的头文件和编译器自带的头文件. 在程序中要使用头文件,需要使用 C 预处理指 ...

  8. selenium+python+unittest实现自动化测试(入门篇)

    本文主要讲解关于selenium自动化测试框架的入门知识点,教大家如何搭建selenium自动化测试环境,如何用selenium+python+unittest实现web页面的自动化测试,先来看看se ...

  9. 为什么阿里Java手册推荐慎用 Object 的 clone 方法来拷贝对象

    图片若无法显示,可至掘金查看https://juejin.im/post/5d425230f265da039519d248 前言 在阿里Java开发手册中,有这么一条建议:慎用 Object 的 cl ...

  10. spring整合web项目

    Web项目如何初始化SpringIOC容器 :思路:当服务启动时(tomcat),通过监听器将SpringIOC容器初始化一次(该监听器 spring-web.jar已经提供),web项目启动时 ,会 ...