二维/三维偏序

定义:

形如 \(x_i<x_j\) 且 \(y_i<y_j\) 之类的约束条件,我们可以称为二维偏序。
逆序对就是一个非常经典的二位偏序。

解决:

如果按照暴力想法,我们 \(O(n^2)\) 的时间枚举 \(i,j\) ,这样太慢了。

处理第 \(i\) 位时,我们已经处理过 \([0,i-1]\) 的数量,那么我们可不可以用一个数据结构记录一下之前的情况呢?

这就引出了二维偏序。

我们把第一维从小到大排序,然后遍历,将第二位插入树状数组中,每次查询,即可解决问题。

其中,因为只拥有这些约束条件,我们需要离散化,减小空间

例子:

就以逆序对作为例子:

先将这个序列离散化,反向排序,此时我们转化成: 记录当前点权值大于之前点的个数之和,也就是正序对。

我们定义一个树状数组为: 记录 \(i\) 前小于 \(val[i]\) 的点权的个数,于是我们就可以在遍历时 插入,查询。

代码:

void add(){...}
int query(){...}
...
for(int i=1;i<=n;i++){
    add(val[i],1);
    ans+=query(val[i]-1);
}

例题:

CF1311F Moving Points

我们分三种情况讨论:

  1. \(x_i<x_j\),\(v_i<=v_j\) : 最小距离就是当前两点距离。
  2. \(x_i<x_j\),\(v_i>v_j\) : 肯定能追上,最小距离为 \(0\)。
  3. \(x_i<x_j\),\(v_i\geq0,v_j\leq 0\) : 相遇问题,最小距离为 \(0\)。

那么这个问题就转换成了 求\(\sum_{i=1}^{n}\sum_{j=i+1}^{n} x_i<x_j\And v_i<v_j\) 的点对的个数。

我们于是定义两个树状数组,第一个 \(t[1]\) 是已经维护了的点的数量 \(A\) ,第二个 \(t[2]\) 是已经维护的点到原点的距离之和 \(B\)

所以公式为 \(\sum_{i=1}^n A*x[i]-B\)
代码:

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define lowbit(x) x&-x
const int N=2e5+5;
int t[2][N],b[N],n,m,ans;
struct node{
    int x,v;
}e[N];
bool cmp(node a,node b){
    return a.x==b.x?a.v<b.v:a.x<b.x;
}
void add(int x,int z,int t[]){
    for(;x<N;x+=lowbit(x)) t[x]+=z;
}
int query(int x,int t[]){
    int res=0;
    for(;x;x-=lowbit(x)) res+=t[x];
    return res;
}
signed main()
{
    cin>>n;
    for(int i=1;i<=n;i++) scanf("%lld",&e[i].x);
    for(int i=1;i<=n;i++) scanf("%lld",&e[i].v),b[i]=e[i].v;
    sort(e+1,e+n+1,cmp); sort(b+1,b+1+n);
    int m=unique(b+1,b+1+n)-b;
    for(int i=1;i<=n;i++) e[i].v=lower_bound(b+1,b+1+m,e[i].v)-b;
    for(int i=1;i<=n;i++){
        add(e[i].v,e[i].x,t[0]); add(e[i].v,1,t[1]);
        ans+=query(e[i].v,t[1])*e[i].x-query(e[i].v,t[0]);
        //已经维护了速度小于v[i]点的数量      已经维护的速度小于v[i]点到原点距离之和
    }
    cout<<ans<<endl;
    return 0;
}