科技——O(n log n) 三维偏序

问题背景

\(n\) 个三元组 \((a_i,b_i,c_i)\),要求满足 \(a_i\le a_j,b_i\le b_j,c_i\le c_j\) 的有序对 \((i,j)\) 数量。
保证不存在两个三元组相同。(存在相同的情况下面会说)。

介绍

因为不会出现重复的数,所以不需要考虑 \((i,j)\) 互相偏序的情况。

\(f_i\) 表示恰好 \(i\) 维偏序的对数,\(g_i\) 表示钦定 \(i\) 维偏序的对数。
那么 \(g_i=\sum_{j=i}^3 C_j^i \times f_j\),二项式反演得 \(f_i=\sum_{j\ge i} (-1)^{j-i} \times C_j^i \times g_j\)
所以:\(f_0=g_0-g_1+g_2-g_3\)
\(g_3=g_0-f_0-g_1+g_2\)
\(g_3=f_3\),所以 \(f_3=g_0-f_0-g_1+g_2\)

\(g_0\)\(g_1\) 直接 \(O(1)\)\(O(n)\) 求,\(g_2\) 做三次二维偏序就可以了。

主要是 \(f_0\) 怎么算。
注意到现在偏序条件是 \(\le\) 所以 \(f_0\) 不一定 \(=f_3\)
比如 \((4,4,4)\) 确实三维偏序 \((2,4,4)\) 但是 \((2,4,4)\) 有两维是偏序 \((4,4,4)\) 的。

所以如果我们能让每一维都变成互不相同的就可以保证 \(f_0=f_3\) 了。
也就是说 \(f_3=\frac{g_0-g_1+g_2}{2}\)

那怎么办呢?
其实只需要分别按某一维为第一关键字排序,其他两维为二,三关键字排序。
将这种条件下的每个三元组的排名作为这一维新的值就可以了。
举个例子: 我们有三个三元组 \((4,4,2),(2,4,4),(4,4,4)\)

  1. 先按照第一维为第一关键字,二,三维为第二,三关键字排序:\((2,4,4),(4,4,2),(4,4,4)\)
    然后用现在的排名替换第一维:\((1,4,4),(2,4,2),(3,4,4)\)
  2. 再按照第二维为第一关键字,一,三维为第二,三关键字排序:\((1,4,4),(2,4,2),(3,4,4)\)
    然后用现在的排名替换第二维:\((1,1,4),(2,2,2),(3,3,4)\)
  3. 最后按照第三维为第一关键字,一,二维为第二,三关键字排序:\((2,2,2),(1,1,4),(3,3,4)\)
    然后用现在的排名替换第三维:\((2,2,1),(1,1,2),(3,3,3)\)

容易证明这样每一维都是排列,并且不会影响 \(f_3\) 最终的值。(但显然会影响 \(f_0,f_1,f_2\),反正你也不去管这三个值)。
具体看最后给出的代码。

而且在这种情况下,\(g_0,g_1\) 都是可以 \(O(1)\) 算的。

代码可能比 CDQ 分治还好写。

补充说明

  1. 如果会出现相同的三元组怎么办?
    相同的三元组答案肯定一样,可以把他们缩到一起(类似于洛谷模板题的处理思路),然后在最后算答案的时候再加上互相偏序的三元组的贡献。

  2. 如果某一维要求 \(\ge\) 怎么办?
    把那一维全部取反即可。

局限性

  1. 不能像 CDQ 分治那样算出每个三元组具体偏序了多少其他三元组。

  2. 偏序条线必须包含 =,即不能处理 > 类型的偏序。
    这一点也很好证明,因为不管你的要求是什么,按照那种方法变换之后的序列都是一样的,无法区分 \(>\)\(\ge\)
    而如果你不变换的话,\((2,4,4)\) 没有一维是偏序 \((4,4,4)\) 的,但是 \((4,4,4)\) 并不是三维偏序 \((2,4,4)\) 的。
    所以 \(f_0\ne f_3\)


下面通过一道典题来给出代码。

例题

题面

给你 \(n\) 个三元组,问有几个有序对 \((i,j)\) 满足第 \(i\) 个三元组可以经过若干次操作变成第 \(j\) 个三元组。
一次操作定义为:选一个数 \(-1\) 再把令两个数 \(+1\)
\(n\le 2e6\)

题解

解个方程就知道 \((d,e,f)\) 可以变成 \((a,b,c)\) 的条件是下面三个数都是非负偶数:

  1. \(a+b-d-e\)
  2. \(a+c-d-f\)
  3. \(b+c-e-f\)

然后用 \((a+b,a+c,b+c)\) 代替 \((a,b,c)\) 根据奇偶性分成 \(8\) 组,每组里面跑三维偏序就可以了。
因为 \(n\le 2e6\) 所以你只能写 \(O(n\log n)\)

code

#include<bits/stdc++.h>
#define LL long long 
using namespace std;                                 
const int N=2e6+5;

inline int read(){
	int w=1,s=0;
	char c=getchar();
	for(;c<'0'||c>'9';w*=(c=='-')?-1:1,c=getchar());
	for(;c>='0'&&c<='9';s=s*10+c-'0',c=getchar());
	return w*s;
}
int n;
struct P{
	int a,b,c;
}p[N];
struct Work{  //三维偏序
	int n=0;
	struct BIT{
		int c[N];
		void init(){memset(c,0,sizeof c);}
		void add(int i,int x,int n){for(;i<=n;i+=(i&(-i))) c[i]+=x;}
		int ask(int i){
			int sum=0;
			for(;i;i-=(i&(-i))) sum+=c[i];
			return sum;
		}
	}Bit;
	struct P{
		int val[3];
	}a[N];
	void insert(int x,int y,int z){a[++n].val[0]=x,a[n].val[1]=y,a[n].val[2]=z;}
	
	LL calc1(int id){ return 1ll*n*(n-1)/2ll; }   //注意到每一维都是排列的情况下,钦定一维偏序的数目可以 O(1) 求
	LL solve1(){return calc1(0)+calc1(1)+calc1(2);}
	LL calc2(int o1,int o2){
		sort(a+1,a+n+1,[&](P u,P v){return u.val[o1]<v.val[o1];});  
		LL res=0;
		Bit.init();
		for(int i=1;i<=n;i++){
			res+=Bit.ask(a[i].val[o2]);
			Bit.add(a[i].val[o2],1,n);
		}
		return res;
	}
	LL solve2(){return calc2(0,1)+calc2(1,2)+calc2(0,2);}
	void Sort(int o1,int o2,int o3){
		sort(a+1,a+n+1,[&](P u,P v){
			if(u.val[o1]!=v.val[o1]) return u.val[o1]<v.val[o1];
			if(u.val[o2]!=v.val[o2]) return u.val[o2]<v.val[o2];
			return u.val[o3]<v.val[o3];
		});
		for(int i=1;i<=n;i++) a[i].val[o1]=i;  //将排名赋值给这一维
	}
	LL work(){ 
		Sort(0,1,2),Sort(1,0,2),Sort(2,0,1);  //重新赋值使得每一维是排列。
		return (1ll*n*(n-1)-solve1()+solve2())/2ll; 
	}
}t[8];
void work(){
	for(int i=1;i<=n;i++){
		int x=p[i].b+p[i].c;	
		int y=p[i].a+p[i].c;	
		int z=p[i].b+p[i].a;	
		t[((x&1)<<2)+((y&1)<<1)+(z&1)].insert(x,y,z);
	}
	LL ans=0;
	for(int i=0;i<8;i++) ans+=t[i].work();
	printf("%lld\n",ans);
}
signed main(){
	freopen("eden.in","r",stdin);
	freopen("eden.out","w",stdout);
	n=read();
	for(int i=1;i<=n;i++){
		p[i]={read(),read(),read()};
	}
	work();
	return 0;
}

CDQ 分治并不会被替代,这是好的。

posted @ 2025-03-31 07:59  Green&White  阅读(107)  评论(0)    收藏  举报