题意 : 给出一个数n(n<500,000), 再给出n个数的序列 a1、a2.....an每一个ai的范围是 0~999,999,999 要求出当通过相邻两项交换的方法进行升序排序时需要交换的次数
分析 : 经典题目, 只要求出这堆乱序序列里面有多少个逆序对即可, 但是数据量很大, 单纯的循环暴力求法肯定是超时的! 利用树状数组可以高效求解, 代码如下, 建议先百度了解一下
int ans = 0; for(int i=1; i<=n; i++){ ans += i - sum(a[i]);//i为已近插入的点的个数 add(a[i], 1);//单点更新+1操作 }
但是, 这道题有个非常棘手的问题, 那就是每一个ai点的最大可能取值很大, 而树状数组的下标正是来存储这些点的, 很显然数组不可能开那么大, 即对于这题来讲有些数据本身很大, 自身无法作为数组的下标保存对应的属性。但是如果这时只是需要这堆数据的相对属性, 那么可以对其进行离散化处理!
离散化:当数据只与它们之间的相对大小有关,而与具体是多少无关时,可以进行离散化。
因为题目解法只关心个个元素的大小关系, 所以可以采用离散化来优化, 核心也就是在相对大小关系不变的情况下将本来很大的数变成很小的数, 这样就能存进树状数组中进行操作了!
下面针对这道题说明如何进行离散化, 首先举个例子来说明离散化前和离散化后元素都变成了什么样子, 以n = 4为例, Order为给出顺序, val为给出的原始值, new val为离散化后的值
Order a1 a2 a3 a4
val 9999999 25 100 10000
new val 4 1 2 3
这样变化的话就能将数压到很小, 最大也不过是n的最大取值500000, 作为数组下标来说完全可以接受!下面说做法 :
1、用一个定义了两个整形变量ord和val的结构体数组arr存储原始输入元素的输入顺序和元素的值。
2、再根据结构体val的值进行升序排序, 于是将结构题里面的ord也进行了重排。
3、刚刚说到要刷新的是val, 于是再定义一个数组MAP将1到n的值作为new val去重新刷新val, 具体就是for(int i=1; i<=n; i++) MAP[arr[i].ord] = i;
值得注意的是, 在进行离散化的时候, 高清各个变量和下表等参加离散化操作的关系, 理清楚之后再进行编码, 否则很容易搞混或者搞乱!刚刚上面的循环我就写成了
for(int i=1; i<=n; i++) MAP[i] = arr[i].ord; 然后纳闷了好久为何WA =_=
#include<stdio.h> #include<string.h> #include<string> #include<map> #include<set> #include<algorithm> #include<iostream> #include<sstream> #include<list> #include<vector> #include<queue> #define lowbit(i) (i&(-i)) using namespace std; const int maxn = 500005; int c[maxn]; typedef struct ARRAY{ int ord, val; }A; A arr[maxn]; int MAP[maxn]; inline void add(int i, int val) { while(i<500001){ c[i] += val; i += lowbit(i); } } inline int sum(int i) { int sum = 0; while(i>0){ sum += c[i]; i -= lowbit(i); } return sum; } bool cmp(const A fir, const A sec) { return fir.val < sec.val; } int main(void) { int n; while(~scanf("%d", &n) && n){ memset(c, 0, sizeof(c)); for(int i=0; i<n; i++){ scanf("%d", &arr[i].val); arr[i].ord = i; } sort(arr, arr+n, cmp); for(int i=0; i<n; i++){ MAP[arr[i].ord] = i; } long long cnt = 0; for(int i=0; i<n; i++){ cnt += i - sum(MAP[i]+1); add(MAP[i]+1, 1); } printf("%lld ", cnt); } return 0; }