首先需要了解逆序对是什么:
逆序对就是如果i > j && a[i] < a[j],这两个就算一对逆序对。其实也就是对于每个数而言,找找排在其前面有多少个比自己大的数。
那么思路就来了,树状数组又一次地优化了这种“需要遍历”的情况。那不就很容易了吗?依次把序列里的数放到树状数组中的A[i]上去(实际是以C[i]形式的插入函数),注意A[i]是以数值大小从小到大排列的。先插入的说明排在序列的前面,那么后插入的就可以看看之前插入的比你大的数有多少,即i-sum(i),其实也就是看序列前面比你大的数有多少个,即找逆序对。
通过树状数组找逆序对的原理(图解,我看了就懂了):
https://blog.csdn.net/ssimple_y/article/details/53744096
了解技巧“离散化”:
这个技巧很有局限性(至少以我目前的认知来说),几乎只适合在树状数组求逆序对来结合使用的。
什么时候要用这个技巧呢?根据以上说的原理,我们知道需要以“数值大小”作为A[i]标准来升序排,所以需要给C数组开元素最大可能值的内存。但万一输入的数据很大,那么C数组岂不是要开很大?内存超限!!而如果要用离散化,只需要给C数组开元素数量的内存。
建立一个结构体包含val和id, val就是输入的数,id表示输入的顺序。然后按照val从小到大排序,如果val相等,那么就按照id排序。
如果没有逆序的话,肯定id是跟i(表示拍好后的顺序)一直一样的,如果有逆序数,那么有的i和id是不一样的。所以,利用树状数组的特性,我们可以简单的算出逆序数的个数。
如果还是不明白的话举个例子。(输入4个数)
输入:9 -1 18 5
输出 3.
输入之后对应的结构体就会变成这样
val:9 -1 18 5
id: 1 2 3 4
排好序之后就变成了
val : -1 5 9 18
id: 2 4 1 3
2 4 1 3 的逆序数 也是3
之后再利用树状数组的特性就可以解决问题了
我觉得很神奇,直接得到“离散化”的结论:把原序列中每个元素的值和下标存到一个结构体node里去,之后把node数组按元素值大小从小到大排序(注意结构体里的重载<运算符的写法 不是男左女右了我结论有误T T 反正考场上试一试即可),这样得到的结点的下标值即是离散化结果,等效于原序列的数值。把这些下标值当成原序列,按照树状数组求逆序对的原理做。
习题:排序
大致思路:
这个就是逆序对的定义呀!求逆序对的模板题。
这道题如果不离散化,C开1e9内存,超限,所以离散化,C只需要开5e5内存。
AC代码:
#include<iostream> #include<bits/stdc++.h> using namespace std; const int maxn=500001; int c[maxn]; struct Node { int v,index; bool operator < (const Node &b) const { return v<b.v; //从小到大排序 } }node[maxn]; int n; void add(int i) { while(i<=n) { c[i]++; i+=i&(-i); } } long long sum(int i) { long long res=0; while(i>0) { res+=c[i]; i-=i&(-i); } return res; } int main() { cin>>n; int a; for(int i=1;i<=n;i++) { scanf("%d",&a); node[i].index=i; node[i].v=a; } sort(node+1,node+1+n); long long ans=0; for(int i=1;i<=n;i++) { add(node[i].index); //离散化结果—— 下标等效于数值 ans+=i-sum(node[i].index); //得到之前有多少个比你大的数(逆序对) } cout<<ans; return 0; }