偏序问题

一维偏序

归并排序即可。

二维偏序*

又称“二维数点”,只需按照 x 坐标排序后树状数组统计即可。

三维偏序

本题的分治本质,就是对于一个长 \(>1\) 的区间,不断将其分为两个区间,分而治之,每次保证两个区间存在 \(L_x\le R_x\),因此需要从整体到局部分治。局部的变化不会影响到总体,总体的变化会影响到局部。

下面阐述对一个区间的操作。

考虑按照 \(x\) 排序,设左右两边分别为 \(L\)\(R\) ,此时对于 \(i\in L,j\in R\),存在 \(x_i<x_j\),我们已经圧掉了一维。考虑如何圧掉第二维。明显,\(L\) 中的点都有可能对 \(j\) 产生贡献。我们分别\(L\)\(R\) 按照 \(y\) 排序。注意,我们目前只考虑 \(L\)\(j\) 的贡献,\(R\) 中的点对 \(j\) 的贡献会在分治中得到体现。设排序后 \(L\) 部分为 \(l_1,l_2,l_3,\ldots ,l_{mid}\)\(R\) 部分同理。先只考虑 \(r_1\)。把 \(i\) 作为 \(L\) 部分的指针,直到枚举到第一个 \(l_i.y>r_1.y\),枚举过程中把 \(l_i.z\) 放入树状数组,统计答案。对于 \(r_2\) ,显然可以延续 \(r_1\) 查询过程中在树状数组上的成果,然后按照刚才的逻辑移动指针即可。

可以发现,本质上对一个区间的操作就是二维偏序的过程,所以总体的时间复杂度为 \(O(n\log^2 n)\)

可以发现,对于 \(k\) 维偏序,利用排序圧掉一维后,借用归并排序的思想,利用 \(k-1\) 维偏序对区间进行操作。

注意,当一次处理完成后要合并区间时,要注意清空树状数组,小常数写法(from syz):


struct fenwick{
	int c[N],vis[N],ti;
	fenwick(){
		memset(c,0,sizeof(c));
		memset(vis,0,sizeof(vis));
	}
	inline void add(int x,int w){
		while(x<=k){
			if(vis[x]!=ti) vis[x]=ti,c[x]=0;
			c[x]+=w;
			x+=lowbit(x);
		}
	}
	inline int sum(int x){
		int res=0;
		while(x){
			res+=c[x]*(vis[x]==ti);
			x-=lowbit(x);
		}
		return res;
	}
	inline void clear(){
		ti++;
	}
}BIT;

运用了可持久化的思想。
然后一个 c[x],我们记录一下其最后一次被修改的时间 ti
如果 ti[x]!=nowti,那么说明这个位置无效

本质上是记录时间。

关于重复数字,我们发现对于:

2
1 1 2
1 1 2

前者的贡献为 \(0\),后者的贡献为 \(1\),其实两者贡献均为 \(1\)。所以我们应该去重后统计重复数字数。

我们也可以这么考虑,对于一个坐标,如果是排序后有 \(p\) 个相同坐标在它后边,那么显然,它会缺少 \(p\) 的贡献——因为后面的每个点都无法对其产生贡献。

注意,不会对自己产生贡献。

关于为什么经过归并排序的方法求解三维偏序后会得到正确的答案——显然,按照 \(x\) 排序后,对于一点,在其前面的点一定会产生贡献——这用数学归纳法可以证明。同样,后面的点一定不会产生贡献——这也可以运用数学归纳法证明。

\(k\) 维偏序

可以发现,对于 \(k\) 维偏序,利用排序圧掉一维后,借用归并排序的思想,利用 \(k-1\) 维偏序对区间进行操作。

时间复杂度 \(O(n\log^{k-1} n)\)

参考:
https://www.luogu.com.cn/problem/solution/P3810
posted @ 2024-02-02 19:48  BYR_KKK  阅读(32)  评论(0编辑  收藏  举报