刚学会树状数组,正好还有个科技是树状数组可以用的:用树状数组求逆序对,码量要比归并排序小。
这里只用到单点更新、区间查询的基础树状数组,没有看后面的同学也可以先学一下这个。
这里直接上一个例子演示一下:
原数组a[]: 5 3 4 2 1
设一个数组t[],按原数组顺序,从前往后将 t[a[i]] ++,并将 ans += (i - quary(a[i]) - 1) 。(quary(x)为x的前缀和)
t[] 的变化过程:
_ _ _ _ 1(a[0] == 5, t[5] ++)ans += 0;
_ _ 1 _ 1(a[0] == 5, t[5] ++)ans += 1;
_ _ 1 1 1(a[0] == 5, t[5] ++)ans += 1;
_ 1 1 1 1(a[0] == 5, t[5] ++)ans += 3;
1 1 1 1 1(a[0] == 5, t[5] ++)ans += 4;
所以逆序对数 = ans = 9。
原理也很好理解,逆序对数就等于每个数前面比它大的数个数之和。
但是,用这种方法求逆序对需要开到数组元素最大值的树状数组(类似于桶排序),较大较稀疏数据建议离散化。
代码实现:
#includeusing namespace std; int a[100010], t[100010]; int n, ans = 0, num; int lowbit(int x){ return x & (-x); } void add_node(int pos, int val){ //节点pos增加val(这里只用到+1) for(int i=pos; i<=n; i+=lowbit(i)){ t[i] += val; } } int quary_node(int pos){ //求节点pos前缀和 int ans = 0; for(int i=pos; i>0; i-=lowbit(i)){ ans += t[i]; } return ans; } int main(){ cin >> n; for(int i=1; i<=n; i++){ scanf("%d", &num); add_node(num, 1); ans += (i - quary_node(num)); } cout << ans; return 0; }
再分享一个压行非常优秀的代码,来自 da32s1da的洛谷AT2829题解:
#includeint n,m,ans; int a[100005]; int main(){ scanf("%d",&n); for(int i=1;i<=n;i++){ scanf("%d",&m); for(int j=m;j;j-=(j&(-j)))ans-=a[j];ans+=i-1; for(int j=m;j<=n;j+=(j&(-j)))a[j]++; } printf("%d\n",ans); }