CDQ分治-陌上花开(附典型错误及原因)
CDQ分治-陌上花开
题目大意
对于给遗传给定的序列:
\]
求:
\]
题解:
CDQ分治,顾名思义就是要进行分治,但是它可以解决比普通分治更多的问题。CDQ分治的整体思想,是:
- 对于一个需要解决问题的区间\([l,r)\),将其一分为二,变为\([l,mid),[mid,r)\)。
- 对于这两个被分开的区间,分别进行递归解决。
- 完成递归解决之后,计算左边对右边的贡献(有的时候可能要计算右边对左边的计算或者互相都要算)
- 完成,统计答案。
在这道题当中,需要解决的是三维偏序的问题,而首先想到的是使用二维树状数组进行求逆序对,将这三维当中的两维当作下标,对最后一维求逆序对。但是这种做法显而易见,空间会爆掉,是开不下的。所以我们需要采取某种算法或者数据结构来代替树状数组的这意味空间。
在这里,就是用了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)
solve(0,mid),solve(mid, n)计算左边对右边的影响(原因:整个序列对于第一维变量单调不下降,所以右边的大数无法对小数产生答案上的贡献)
对\([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\)这三组数据位置颠倒了。而解决这个问题的方式就是更正排序方式:
- 将\(y\)作为第一排序关键字
- 将\(z\)作为第二排序关键字
- 将\(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分治-陌上花开(附典型错误及原因)的更多相关文章
- CDQ分治 陌上花开(三维偏序)
CDQ分治或树套树可以切掉 CDQ框架: 先分 计算左边对右边的贡献 再和 所以这个题可以一维排序,二维CDQ,三维树状数组统计 CDQ代码 # include <stdio.h> # i ...
- cdq分治 陌上花开(内无题解)
由于有归并排序 要注意是对原来的那个元素进行更新答案和删除操作 而不是占据原来那个元素下标的元素
- 【BZOJ-3262】陌上花开 CDQ分治(3维偏序)
3262: 陌上花开 Time Limit: 20 Sec Memory Limit: 256 MBSubmit: 1439 Solved: 648[Submit][Status][Discuss ...
- 【BZOJ3262】陌上花开(CDQ分治)
[BZOJ3262]陌上花开(CDQ分治) 题解 原来放过这道题目,题面在这里 树套树的做法也请点上面 这回用CDQ分治做的 其实也很简单, 对于第一维排序之后 显然只有前面的对后面的才会产生贡献 那 ...
- bzoj3262陌上花开 cdq分治
3262: 陌上花开 Time Limit: 20 Sec Memory Limit: 256 MBSubmit: 2794 Solved: 1250[Submit][Status][Discus ...
- 洛谷P3810 陌上花开 CDQ分治(三维偏序)
好,这是一道三维偏序的模板题 当然没那么简单..... 首先谴责洛谷一下:可怜的陌上花开的题面被无情的消灭了: 这么好听的名字#(滑稽) 那么我们看了题面后就发现:这就是一个三维偏序.只不过ans不加 ...
- Luogu 3810 & BZOJ 3262 陌上花开/三维偏序 | CDQ分治
Luogu 3810 & BZOJ 3263 陌上花开/三维偏序 | CDQ分治 题面 \(n\)个元素,每个元素有三个值:\(a_i\), \(b_i\) 和 \(c_i\).定义一个元素的 ...
- 【BZOJ3262】陌上花开 cdq分治
[BZOJ3262]陌上花开 Description 有n朵花,每朵花有三个属性:花形(s).颜色(c).气味(m),又三个整数表示.现要对每朵花评级,一朵花的级别是它拥有的美丽能超过的花的数量.定义 ...
- 【BZOJ 3262】 3262: 陌上花开 (CDQ分治)
3262: 陌上花开 Description 有n朵花,每朵花有三个属性:花形(s).颜色(c).气味(m),又三个整数表示.现要对每朵花评级,一朵花的级别是它拥有的美丽能超过的花的数量.定义一朵花A ...
随机推荐
- json_encode转化索引数组之后依然还是数组的问题
小坑问题:直接上图 解决方法:(json_encode加入第二个参数) JSON_FORCE_OBJECT
- Java进阶学习(1)之类与对象(下)
类与对象 函数与调用 函数是通过对象来调用的 this 是成员函数的特殊的固有的本地变量 它表达了调用这个函数的那个对象 调用函数 通过 . 运算符,调用某个对象的函数 在成员函数内部直接调用自己(t ...
- Python(三)enumerate函数
原文链接:https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/00143177932 ...
- 「JSOI2013」哈利波特和死亡圣器
「JSOI2013」哈利波特和死亡圣器 传送门 首先二分,这没什么好说的. 然后就成了一个恒成立问题,就是说我们需要满足最坏情况下的需求. 那么显然在最坏情况下伏地魔是不会走回头路的 因为这显然是白给 ...
- 老段带你学鸟哥Linux视频教程 包含基础班+提高班
老段带你学鸟哥Linux视频教程 包含基础班+提高班,附带pdf文档. 目录结构如下: 目录:/-老段带你学鸟哥Linux视频教程 [.9G] ┣━━老段带你学鸟哥-服务器篇 [1009.4M] ┃ ...
- 14 用DFT计算线性卷积
用DFT计算线性卷积 两有限长序列之间的卷积 我们知道,两有限长序列之间的卷积可以用圆周卷积代替,假设两有限长序列的长度分别为\(M\)和\(N\),那么卷积后的长度为\(L=M+N-1\),那么用 ...
- 【C#】图解如何添加引用using MySql.Data.MySqlClient;
使用C#连接MySQL时,经常会用到命名空间using MySql.Data.MySqlClient; 这说明VS中没有添加引用,解决方法如下: 1,可点击以下两个链接的其中任何一个,下载MySQL. ...
- 拼接 字典序min
给定一个字符串类型的数组strs,找到一种拼接方式,使得把所有字符串拼起来之后形成的字符串具有最低的字典序. 输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个 ...
- Update(Stage4):Spark原理_运行过程_高级特性
如何判断宽窄依赖: =================================== 6. Spark 底层逻辑 导读 从部署图了解 Spark 部署了什么, 有什么组件运行在集群中 通过对 W ...
- Session服务器之Memcached
材料:两台Tomcat(接Session复制一起做) 第一台Tomcat:IP为130 [root@localhost ~]# yum install libevent memcached -y ...