树状数组专题(一)-最少的交换(树状数组,离散化,逆序数)
-
算法思路:求逆序数的和
如果说只是交换相邻的两个数字。那么就是这个序列的逆序数
-
假设序列个数为n,我们先把最大的数换到最后,因为是相邻数字交换,所以把最大数交换到最后,需要交换的次数为最大数后的数字个数。
-
当完成最大数的交换后,可以将最大数从序列中划去不管了,即此时序列个数为n-1了,我们再在该序列中找到一个最大数,进行相同操作。
-
所以使整个序列有序的交换次数为,这个序列的所有逆序总数。
比如4,3,2,1。
(4,3) (4,2) (4,1),有3个逆序,交换后 3,2,1,4
(3,2) (3,1),有2个逆序,交换后2,1,3,4(2,1),有1个逆序,交换后1,2,3,4
-
-
离散化
-
现在由于999999999这个数字相对于500000这个数字来说是很大的,
所以如果用数组位存储的话,那么需要999999999的空间来存储输入的数据。
这样是很浪费空间的,题目也是不允许的,所以这里想通过离散化操作,
使得离散化的结果可以更加的密集。
简言之就是开一个大小为这些数的最大值的树状数组 -
离散化是一种常用的技巧,有时数据范围太大,可以用来缩放我们能处理的范围;
离散化其实就是建立一个给定数与1到N数组的一个映射
-
当然用map可以建立,效率可能低点
-
这里用一个结构体
struct Node{ int val; // 数值 int pos; // 原始下标 }p[510000]; // 和一个数组a[510000]
其中val就是原输入的值,pos是下标,然后对结构体按val进行排序。
for(int i = 1 ; i < N ; i++) a[p[i].pos] = i; // 排序后,原来的下标和现在的下标(已有序)一一对应,由于原来的下标与数值用结构体绑定,即原来的数值与现在有序的下标一一对应。即a[i]中存放原来未排序数值应该存放的下标
-
离散后,怎么使用离散后的结果数组来进行树状数组操作,计算出逆序数?
如果数据不是很大, 可以一个个插入到树状数组中,
每插入一个数, 统计比他小的数的个数,
对应的逆序为 i- sum( a[i] ),
其中 i 为当前已经插入的数的个数,
sum( a[i] )为比 a[i] 小的数的个数,
i- sum( a[i] ) 即比 a[i] 大的个数, 即逆序的个数
-
-
-
代码如下
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> const int maxn = 500000+10; using namespace std; struct Node{ int val; int pos; }p[maxn]; int a[maxn]; // 离散化后的数组,即真正需要操作的数组 int c[maxn]; // 树状数组 int lowbit(int x){ return x & (-x); } int getSum(int x){ int sum = 0; for(int i = x ; i > 0 ; i -= lowbit(i)) sum += c[i]; return sum; } void update(int x,int val){ for(int i = x ; i < maxn ; i += lowbit(i)){ c[i] += val; } } bool cmp(Node a,Node b){ return a.val < b.val; } int main(){ long n; while(scanf("%ld",&n)!=EOF && n){ memset(c,0,sizeof(c)); for(int i = 1 ; i <= n ; i++){ scanf("%ld",&p[i].val); p[i].pos = i; } sort(p+1,p+n+1,cmp); for(int i = 1 ; i <= n ; i++){ a[p[i].pos] = i; } long long ans = 0; for(int i = 1 ; i <= n ; i++){ update(a[i],1); ans += i - getSum(a[i]); } printf("%lld\n",ans); } }
-
运行结果