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 ...
随机推荐
- python pylab.plot() 方法使用
Python 中用pylab模块, pylab.plot() 函数,绘制折线统计图 import pylab as pl x = [, , , ] y = [, , , ] ''' plot参数说明: ...
- 1011 World Cup Betting
Title:1011 World Cup Betting 1. 注意点 比较简单,没有注意点 2. python3代码 def func(output): max = 0 index = -1 lin ...
- oracle 基础sql语句
修改date日期时间: update T2_FOODS_STORAGE_IN set create_time =to_date('2020-01-15 12:30:20','yyyy-mm-dd hh ...
- eclipse中使用maven update project功能后,默认又回到了jre 1.5的解决方案
在maven项目中的pom.xml中添加以下节点,进行jre版本的配置,配置完后再进行项目更新后,并不会自动切换到jre1.5 添加在pom的url标签后面 <build> ...
- 获得APP的包名package和activity
方法一: Aapt dumpbadging xxxx.apk(包的路径) 第一个框为包名 第二个框为主Activity名 方法二: 如果你装了Appium 可以这么操作下 进入设置页,选择APK 路 ...
- 【jQuery基础】
" 目录 #. 介绍 1. 优势 2. 版本 3. jQuery对象 #. 查找标签 1. 选择器 /. 基本选择器 /. 层级选择器 /. 基本筛选器 /. 使用jQuery实现弹框 / ...
- scrapy import CrawlSpider 报错
from scrapy.spider import CrawlSpider 报错 import module CrawlSpider error 看了下以前一直用的scrapy0.14.1 使用的是B ...
- VMWare tools
一.首先是安装VMWare tools1.以ROOT身份进入LINUX2.在虚拟机软件VMWARE状态栏中,点击 SETTING菜单下的ENABLE VMWARE TOOLS子菜单,此时在linux的 ...
- 棍子Sticks(poj_1011)[经典搜索]
[题意描述] George用相同的长度棍子,将他们随机切成最多64个单位的长度,现在,他想回到原来的状态,但他忘了他原来的多少根,以及他们原本是多长.请帮助他和设计一个程序,计算最小的可能的原始长度. ...
- [LeetCode] 735. Asteroid Collision
行星碰撞. 题意是给一个数组 asteroids,表示在同一行的行星.对于数组中的每一个元素,其绝对值表示行星的大小,正负表示行星的移动方向(正表示向右移动,负表示向左移动).每一颗行星以相同的速度移 ...