题目
https://www.luogu.com.cn/problem/P1908
题目分析
树状数组的使用原因
可以开一个数组c[maxn],来记录前面数据的出现情况,初始化为0;当数据a出现时,就令c[a]=1。这样的话,欲求某个数a的逆序数,只需要算出在当前状态下c[a+1,maxn]中有多少个1,因为这些位置的数在a之前出现且比a大。这其实就是明显的单点更新(数据a出现时,就令c[a]=1),区间查询(c[a+1,maxn]中有多少个1)——树状数组
而getsum(c[a+1,maxn])等价于 i - getsum( c[i] ),其中 i 为当前已经插入的数的个数, getsum( c[i] )为比 c[i] 小的数的个数,i- getsum( c[i] ) 即比c[i] 大的个数, 即逆序的个数。
最后需要把所有逆序数求和,就是在插入的过程中边插入边求和.离散化原理
输入: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
这样原来是输入9,就把t[9]置为1,输入18,就把t[18]置为1,最后遍历t数组到输入数字的最大值,统计出现过的次数,如果输入一个1000000,就要遍历t数组到t【1000000】
现在使用id等价代替原数组,只需要输入元素个数的空间即可,节省了数组空间
相等元素处理
如果遇到相等的元素,就使用id(即输入的顺序)区分,这样后输入的相等元素的id大,在进行id等价转换的时候它的转换情况偏小(自己的id大了,转换后相对于自己的数大了,那么找自己前面比自己大的数就有可能结果偏小),但是前面还有一个与自己数字相等的元素,它的id是准确的,也就是最后不会影响结果
代码
#include<iostream> #include<cstdio> #include<algorithm> using namespace std; struct node { long long val; int id; }a[500005]; long long c[500005]; int n; bool cmp(struct node &a, struct node&b) { if (a.val == b.val)return a.id < b.id; return a.val < b.val; } int lowbit(int x) { return x&(-x); } void update(int i, int k) { while (i <= n) { c[i] += k; i += lowbit(i); } } long long getsum(int i) { long long res = 0; while (i > 0) { res += c[i]; i -= lowbit(i); } return res; } int main() { int aa, bb, cc,dd; scanf("%d", &n); for (int i = 1; i <= n; i++) { scanf("%lld", &a[i].val); a[i].id = i; } sort(a + 1, a + n + 1, cmp); long long answer = 0; for (int i = 1; i <= n; i++) { update(a[i].id,1); answer += i - getsum(a[i].id);//用来存储原数第i个数的order下标是什么 } printf("%lld ", answer); }
参考:https://www.jianshu.com/p/8a4081f0ec20