洛谷 P1908 逆序对 Label:归并排序||树状数组 不懂
题目描述
猫猫TOM和小老鼠JERRY最近又较量上了,但是毕竟都是成年人,他们已经不喜欢再玩那种你追我赶的游戏,现在他们喜欢玩统计。最近,TOM老猫查阅到一个人类称之为“逆序对”的东西,这东西是这样定义的:对于给定的一段正整数序列,逆序对就是序列中ai>aj且i<j的有序对。知道这概念后,他们就比赛谁先算出给定的一段正整数序列中逆序对的数目。
输入输出格式
输入格式:
第一行,一个数n,表示序列中有n个数。
第二行n个数,表示给定的序列。
输出格式:
给定序列中逆序对的数目。
输入输出样例
6 5 4 2 6 3 1
11
说明
对于50%的数据,n≤2500
对于100%的数据,n≤40000。
代码:解法一
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 using namespace std; 6 int N,a[100005],temp[100005],cnt; 7 8 void print(){ 9 for(int i=1;i<=6;i++){ 10 printf("%d ",a[i]); 11 } 12 puts(""); 13 } 14 15 void merge_sort(int l,int r){ 16 if(l>=r) return; 17 int mid=(l+r)>>1; 18 merge_sort(l,mid);merge_sort(mid+1,r); 19 20 21 int i=l,j=mid+1,point=l; 22 while(i<=mid&&j<=r){ 23 if(a[i]>a[j]){ 24 temp[point++]=a[j++]; 25 cnt+=(mid-i+1); 26 } 27 else{ 28 temp[point++]=a[i++]; 29 } 30 } 31 32 while(i<=mid) temp[point++]=a[i++]; 33 while(j<=r) temp[point++]=a[j++]; 34 35 for(int k=l;k<=r;++k) 36 a[k]=temp[k]; 37 // print(); 38 } 39 40 int main(){ 41 // freopen("01.txt","r",stdin); 42 43 scanf("%d",&N); 44 for(int i=1;i<=N;++i) 45 scanf("%d",&a[i]); 46 47 merge_sort(1,N); 48 49 printf("%d\n",cnt); 50 return 0; 51 }暴力枚举的话,可以过两个点~
转载题解如下:http://blog.csdn.net/acdreamers/article/details/16849761
归并排序是将数列a[l,h]分成两半a[l,mid]和a[mid+1,h]分别进行归并排序,然后再将这两半合并起来。
在合并的过程中(设l<=i<=mid,mid+1<=j<=h),当a[i]<=a[j]时,并不产生逆序数;当a[i]>a[j]时,在
前半部分中比a[i]大的数都比a[j]大,将a[j]放在a[i]前面的话,逆序数要加上mid+1-i。因此,可以在归并
排序中的合并过程中计算逆序数.
解法二:树状数组+离散化
1 #include <iostream> 2 #include <cstring> 3 #include <cstdio> 4 #include <algorithm> 5 using namespace std; 6 7 const int N = 500005; 8 9 struct Node 10 { 11 int val; 12 int pos; 13 }; 14 15 Node node[N]; 16 int c[N], reflect[N], n; 17 18 bool cmp(const Node& a, const Node& b) 19 { 20 return a.val < b.val; 21 } 22 23 int lowbit(int x) 24 { 25 return x & (-x); 26 } 27 28 void update(int x) 29 { 30 while (x <= n) 31 { 32 c[x] += 1; 33 x += lowbit(x); 34 } 35 } 36 37 int getsum(int x) 38 { 39 int sum = 0; 40 while (x > 0) 41 { 42 sum += c[x]; 43 x -= lowbit(x); 44 } 45 return sum; 46 } 47 48 int main() 49 { 50 while (scanf("%d", &n) != EOF && n) 51 { 52 for (int i = 1; i <= n; ++i) 53 { 54 scanf("%d", &node[i].val); 55 node[i].pos = i; 56 } 57 sort(node + 1, node + n + 1, cmp); //排序 58 for (int i = 1; i <= n; ++i) reflect[node[i].pos] = i; //离散化 59 for (int i = 1; i <= n; ++i) c[i] = 0; //初始化树状数组 60 long long ans = 0; 61 for (int i = 1; i <= n; ++i) 62 { 63 update(reflect[i]); 64 ans += i - getsum(reflect[i]); 65 } 66 printf("%lld\n", ans); 67 } 68 return 0; 69 }转载自http://blog.csdn.net/alongela/article/details/8142965
给定n个数,要求这些数构成的逆序对的个数。除了用归并排序来求逆序对个数,还可以使用树状数组来求解。
树状数组求解的思路:开一个能大小为这些数的最大值的树状数组,并全部置0。从头到尾读入这些数,每读入一个数就更新树状数组,查看它前面比它小的已出现过的有多少个数sum,然后用当前位置减去该sum,就可以得到当前数导致的逆序对数了。把所有的加起来就是总的逆序对数。
题目中的数都是独一无二的,这些数最大值不超过999999999,但n最大只是500000。如果采用上面的思想,必然会导致空间的巨大浪费,而且由于内存的限制,我们也不可能开辟这么大的数组。因此可以采用一种称为“离散化”的方式,把原始的数映射为1-n一共n个数,这样就只需要500000个int类型的空间。
离散化的方式:
struct Node
{
int val;
int pos;
};
Node node[500005];
int reflect[500005];
val存放原数组的元素,pos存放原始位置,即node[i].pos = i。
把这些结构体按照val的大小排序。
reflect数组存放离散化后的值,即reflect[node[i].pos] = i。
这样从头到尾读入reflect数组中的元素,即可以保持原来的大小关系,又可以节省大部分空间。
这个可以辅助理解
http://blog.csdn.net/cattycat/article/details/5640838
树状数组实际上还是一个数组,只不过它的每个元素保存了跟原来数组的一些元素相关的结合值。
若A为原数组,定义数组C为树状数组。C数组中元素C[ i ]表示A[ i –lowbit( i ) + 1]至A[ i ]的结合值。
lowbit(i)是i的二进制中最后一个不为零的位数的2次方,可以这样计算
lowbit(i)=x&(-x)
lowbit(i)=x&(x^(x-1))