偏序问题学习指南

典题合集

前置芝士

一维偏序

直接排序即可。

二维偏序

有(x,y),按先xy顺序排序,然后将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;
	}
}
posted @ 2023-11-21 16:11  White_Sheep  阅读(41)  评论(0编辑  收藏  举报