二維/三維偏序
定義:
形如 \(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;
}