\[\huge\texttt{0/1 Trie} \]
\[\LARGE\texttt{\#1-Count Inversions} \]
\[\large\texttt{Problem} \]
\[\large\texttt{Idea} \]
考虑逆序对总个数的表达式:
\[\sum_{i=2}^{n}\sum_{j=1}^{i-1}\left[a_i<a_j\right] \]
显然,通过式子直接暴力,复杂度为 \(\mathcal{O}(n^2)\),显然错误。
考虑对于内层循环进行优化,主要问题在于如果最原始地进行比较大小需要一个一个进行比较。
于是,想到高位更大的数字一定更大,我们可以从高位依次比较。
于是我们可以考虑使用字典树,利用字典树存储二进制下每个数的每一位数。
\[\large\texttt{Solution} \]
对于每个数一次插入字典树,具体参考模板,以下仅赘述统计逆序对的部分。
假设当前插入的数为 \(x\):
- 若当前遍历的位数上的编码为 \(\texttt{0}\),则这一位编码为 \(\texttt{1}\) 的数能与 \(x\) 构成逆序对,更新答案,继续向下一位遍历。
- 若当前遍历的位数上的编码为 \(\texttt{1}\),则继续向下一位遍历。
具体统计可以记录每个节点下存有几个数。
这里注意一个细节,每个数二进制形式的长度不同,需要统一长度,一种方法是都将长度变为 \(30\),在前面填 \(\texttt{0}\) 即可。
时间复杂度 \(\mathcal{O}(n)\),实则如果全部统一成 \(30\) 位是有一个大常数 \(30\) 的,写标准复杂度应该是 \(\mathcal{O}\left(n\log\left(\max\limits_{1\le i\le n}\left\{a_i\right\}\right)\right)\)。
当然还有其它解决长度不统一的方法。
\[\large\texttt{Code} \]
cpp
#include<bits/stdc++.h>
using namespace std;
int n,nex[15000005][2],cnt,siz[15000005];
long long ans;
void insert(int s){
int p=0;
for(int i=29;i>=0;i--){
int c=(s>>i)&1;
if(c==0)ans+=siz[nex[p][1]];
if(!nex[p][c])nex[p][c]=++cnt;
p=nex[p][c];
siz[p]++;
}
}
signed main(){
cin>>n;
for(int a,i=1;i<=n;i++){
cin>>a;
insert(a);
}
cout<<ans;
return 0;
}
又短又好写。