1、归并排序求逆序数
http://acm.nyist.net/JudgeOnline/problem.php?pid=117
在归并排序的过程中,比较关键的是通过递归,将两个已经排好序的数组合并,此时,若a[i] > a[j],则i到m之间的数都大于a[j],合并时a[j]插到了a[i]之前,此时也就产生的m-i+1个逆序数,而小于等于的情况并不会产生。
1 #include<stdio.h> 2 #define maxn 1000005 3 int a[maxn],temp[maxn]; 4 long long sum; 5 void merge(int l,int r,int m){ 6 int i = l, j = m + 1,k = l; 7 while(i<=m&&j<=r){ 8 if(a[i] > a[j]){ 9 sum += m - i + 1; 10 temp[k++] = a[j++]; 11 }else{ 12 temp[k++] = a[i++]; 13 } 14 } 15 while(i<=m) 16 temp[k++] = a[i++]; 17 while(j<=r) 18 temp[k++] = a[j++]; 19 for(i = l;i<=r;i++) 20 a[i] = temp[i]; 21 } 22 void mergesort(int l,int r){ 23 if(l<r){ 24 int m = (l + r) / 2; 25 mergesort(l,m); 26 mergesort(m+1,r); 27 merge(l,r,m); 28 } 29 } 30 int main(){ 31 int t; 32 scanf("%d",&t); 33 while(t--){ 34 int n; 35 scanf("%d",&n); 36 for(int i = 0;i < n;i++){ 37 scanf("%d",&a[i]); 38 } 39 sum = 0; 40 mergesort(0,n-1); 41 printf("%lld\n",sum); 42 } 43 return 0; 44 }
2、线段树求逆序数
用线段树来求逆序数的思路关键在于,线段树是维护一个区间的,所以,对于这种连续区间求逆序数,完全可以判断当插入一个新的数字时,若比它大的数字已经插入了,说明排在了它的前面,也就是产生了这些逆序数。
这道题还有一个问题就是,当这个数列前面几个数移到后面后这个数列的逆序数将怎样变化,这里我引用博主ACdreamer的话来解释,原博:http://blog.csdn.net/ACdreamers/article/details/8577553
若abcde...的逆序数为k,那么bcde...a的逆序数是多少?我们假设abcde...中小于a的个数为t , 那么大于a的个数就是n-t-1,当把a移动最右位时,原来比a
大的现在都成了a的逆序对,即逆序数增加n-t-1,但是原来比a小的构成逆序对的数,现在都变成了顺序,因此逆序对减少t ,所以新序列的逆序数为 k +=
n - t - t -1,即k += n-1-2 * t , 于是我们只要不断移位(n次),然后更新最小值就可以了
1 #include<stdio.h> 2 #include<string.h> 3 #include<algorithm> 4 #define maxn 5005 5 using namespace std; 6 int tree[maxn<<2],n,k; 7 int t[maxn];//存储输入的数列 8 int sum; 9 void init(){ 10 k = 1; 11 while(k<n) 12 k <<= 1; 13 memset(tree,0,sizeof(tree)); 14 } 15 void upgrade(int index){ 16 index = k + index -1; 17 tree[index]++; 18 index /= 2; 19 while(index){ 20 tree[index] = tree[index*2] + tree[index*2+1]; 21 index /= 2; 22 } 23 } 24 void query(int a,int b,int index,int l,int r){ 25 if(a<=l&&b>=r){ 26 sum += tree[index]; 27 return; 28 } 29 int m = (l+r) / 2; 30 if(a<=m) 31 query(a,b,index*2,l,m); 32 if(b>m) 33 query(a,b,index*2+1,m+1,r); 34 } 35 int main(){ 36 while(scanf("%d",&n)!=EOF){ 37 init(); 38 sum = 0; 39 //求出初始数列的逆序数 40 //由于线段树建立是,根节点是1,维护的区间是1到n,所以读入的每一个数更新到线段树里时都要加一 41 for(int i = 0;i<n;i++){ 42 scanf("%d",&t[i]); 43 query(t[i]+1,n,1,1,k); 44 upgrade(t[i]+1); 45 } 46 int ans = sum; 47 for(int i = 0;i<n;i++){ 48 sum += n-2*t[i]-1; 49 ans = min(ans,sum); 50 } 51 printf("%d\n",ans); 52 } 53 return 0; 54 }
3、树状数组求逆序数
还是上面那道题
由于树状数组的特性,求和是从当前节点往前求,所以,这里要查询插入当前数值之时,要统计有多少个小于该数值的数还没插入,这些没插入的数,都会在后面插入,也就形成了逆序数。
1 #include<stdio.h> 2 #include<string.h> 3 #include<algorithm> 4 #define maxn 5005 5 using namespace std; 6 int tree[maxn],t[maxn],n; 7 int lowbit(int n){ 8 return n&(-n); 9 } 10 void add(int index){ 11 while(index<=n){ 12 tree[index]++; 13 index += lowbit(index); 14 } 15 } 16 int sum(int index){ 17 int temp = 0; 18 while(index){ 19 temp += tree[index]; 20 index -= lowbit(index); 21 } 22 return temp; 23 } 24 int main(){ 25 while(scanf("%d",&n)!=EOF){ 26 memset(tree,0,sizeof(tree)); 27 int ans = 0; 28 for(int i = 1;i<=n;i++){ 29 //树状数组维护1~n,所以传参的时候要加1 30 scanf("%d",&t[i]); 31 add(t[i]+1); 32 ans += i- sum(t[i]+1); 33 } 34 int temp = ans; 35 for(int i = 1;i<=n;i++){ 36 ans += n-2*t[i]-1; 37 temp = min(temp,ans); 38 } 39 printf("%d\n",temp); 40 } 41 return 0; 42 }
4、离散化
http://poj.org/problem?id=2299
有些题目给出的数据相差很大,用线段树或树状数组来做可能浪费很大的空间,这时就需要离散化的技巧。由于其他地方都差不太多,所以只大概写一些离散化的方法。
对于数列9 1 0 5 4来说,他们的下标为1 2 3 4 5,离散化就是把这个数列映射为5 2 1 4 3,最小的数对应1,次小的对应2…,所以先按数值由小到大排序,变为0 1 4 5 9,他们的下标为3 2 5 4 1,先取数值最小的数,它要映射为1,他的下标是index,在aa数组中对应的位置赋值就好了。
1 #include<stdio.h> 2 #include<algorithm> 3 using namespace std; 4 struct node{ 5 int v; 6 int index; 7 }; 8 node a[100]; 9 int aa[100];//离散化后的数组 10 int n; 11 bool cmp(node a,node b){ 12 return a.v < b.v; 13 } 14 int main(){ 15 scanf("%d",&n); 16 for(int i = 1;i<=n;i++){ 17 scanf("%d",&a[i].v); 18 a[i].index = i; 19 } 20 sort(a+1,a+1+n,cmp); 21 for(int i = 0;i<=n;i++) 22 aa[a[i].index] = i; 23 return 0; 24 }