二维/三维偏序
定义:
形如 \(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);
}
例题:
我们分三种情况讨论:
- \(x_i<x_j\),\(v_i<=v_j\) : 最小距离就是当前两点距离。
- \(x_i<x_j\),\(v_i>v_j\) : 肯定能追上,最小距离为 \(0\)。
- \(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;
}