偏序问题学习指南
典题合集
前置芝士
一维偏序
直接排序即可。
二维偏序
有(x,y),按先x后y顺序排序,然后将y值用树状数组统计。
三维偏序
三维偏序
[problem description]
有 $ n $ 个元素,第 $ i $ 个元素有 $ a_i,b_i,c_i $ 三个属性,设 $ f(i) $ 表示满足 $ a_j \leq a_i $ 且 $ b_j \leq b_i $ 且 $ c_j \leq c_i $ 且 $ j \ne i $ 的 \(j\) 的数量。
对于 $ d \in [0, n) $,求 $ f(i) = d $ 的数量。
[input]
第一行两个整数 $ n,k $,表示元素数量和最大属性值。
接下来 $ n $ 行,每行三个整数 $ a_i ,b_i,c_i $,分别表示三个属性值。
[output]
$ n $ 行,第 $ d + 1 $ 行表示 $ f(i) = d $ 的 $ i $ 的数量。
[a.in]
10 3
3 3 3
2 3 3
2 3 1
3 1 1
3 1 2
1 3 1
1 1 2
1 2 2
1 3 2
1 2 1
[a.out]
3
1
3
0
1
0
1
0
0
1
[datas]
$ 1 \leq n \leq 10^5$,$1 \leq a_i, b_i, c_i \le k \leq 2 \times 10^5 $
[solved]
将待处理的序列分成两份,分别按y值排序和统计。(分治思想,类似逆序对)
[统计]
两部分中:左半部分x都比右半部分的小,因此只需要统计右半部分对左半部分的贡献即可。用树状数组统计z值。因为两边y值都是有序的,所以维护一个左边的指针,每次将y值小于等于右边当前位置的元素扔进树状数组统计,然后统计z值比它小的即可。
处理前必须去重,因为是小于等于,要防止同样的元素被切进两个区域导致无法统计答案。最后统计答案时,要针对cnt再进行更改。
[注意事项]
不能每次新建一个树状数组或清空树状数组,要一步一步撤销操作,否则每次都要花费O(n)时间清空,总时间复杂度变成\(O(n^2)\)。
递归前一半,判断前一半对于后一半的影响,递归后一半。
1)对x关键词进行排序
首先我们可以以x作为第一关键字进行排序,(y和z作为次要关键字),这样我们就可以保证从左到右是以x单调递增的。
2)递归
然后我们将这个区间一分为二,分别对于每一个序列以y作为第一关键字排序,这样一来我们得到的两个序列拥有以下的性质:
1.左序列的任意x值都小于右序列的任意x值
2.每一个序列里的y值都是单调递增的
最后我们分别在序列头和序列中点设定两个指针j和i,对于每一个i,j都从上一个不满足条件的地方开始枚举,将j对应的y小于i对应的y的j加入树状数组中,最后只要判断z值是否满足条件就可以了。
1.由于原序列可能会有重复的数,我们要先进行去重,并储存每一个数都代表了几个相同的数。
2.每一次i的增加都要清空树状数组,只要加上之前加的相反数就可以了。
const int M = 200010;
const int N = 100010;
struct node {
int x, y, z, w;//x,y,z:坐标,w:相同的个数
int ans;//表示有多少偏序小于当前偏序
};
node a[N], b[N]; //b:原数组,a:去重后的数组
int idx, ans[M];
int n, m;
bool cmp1(node u, node v) { //x,y,z,第一维排序
if (u.x == v.x) {
if (u.y == v.y)
return u.z < v.z;
return u.y < v.y;
}
return u.x < v.x;
}
bool cmp2(node u, node v) {//y,z,第二维排序
if (u.y == v.y)
return u.z < v.z;
return u.y < v.y;
}
struct Fenwick_Tree {
int sum[M], n; //空间自行更改
// Fenwick_Tree(int n): n(n) {}
int lowbit(int x) {
return x & -x;
}
int query(int i) {
int res = 0;
for (; i; i -= lowbit(i)) res += sum[i];
return res;
}
void change(int i, int k) {
for (; i <= n; i += lowbit(i)) sum[i] += k;
}
} tr;
void cdq(int l, int r) {
if (l == r) return;
cdq(l, mid);
cdq(mid + 1, r);
sort(a + l, a + mid + 1, cmp2);
sort(a + mid + 1, a + r + 1, cmp2);
int i = mid + 1, j = l;
while (i <= r) {
while (a[j].y <= a[i].y && j <= mid) {
tr.change(a[j].z, a[j].w);
j += 1;
}
a[i].ans += tr.query(a[i].z);
i += 1;
}
for (i = l; i < j; i++) {
tr.change(a[i].z, -a[i].w);
}
}
void solve() {
//freopen("a.in","r",stdin);
//freopen("a.out","w",stdout);
cin >> n >> m;
tr.n = m;
for (int i = 1; i <= n; i++) {
cin >> b[i].x >> b[i].y >> b[i].z;
}
sort(b + 1, b + n + 1, cmp1);
int c = 0;
for (int i = 1; i <= n; i++) {
c++;
if (b[i].x != b[i + 1].x || b[i].y != b[i + 1].y || b[i].z != b[i + 1].z )
a[++idx] = b[i], a[idx].w = c, c = 0;
}
cdq(1, idx);
for (int i = 1; i <= idx; i++) {
ans[a[i].ans + a[i].w - 1] += a[i].w; //这个地方不太好理解:cnt【x】就是储存f【i】= x的个数,x就等于i的答案加上它重复的个数(可以取等)减去本身
}
for (int i = 0; i < n; i++) {
cout << ans[i] << endl;
}
}