静态求逆序对就是没有修改的序列求逆序对,如果有修改就要用cdq了Orz
静态求逆序对的方法本弱只会2种,归并排序求逆序对和树状数组求逆序对。复杂度都是nlogn
归并排序求逆序对
代码只比普通归并排序多了一句话。归并排序是一直分治下去,直到分到一个小区间里只有2个数的时候在开始排序,而排序的方法是左边和右边都排好队,(以从小到大排序为例),然后每次从2队的队首取出一个较小的元素,放到一个空数组里,这样一直到2队的元素都取完了也就排好了,当然排好的数列存在空数组里,所以还要赋值回来,这样的话,也就保证了,每次排序时的左右2队都是有序的,所以每次取出的2个队里较小的那个也是当前所有未排序元素中最小的那个。举个例子:1 5 8 3,他会分到1 5和8 3,然后1 5 就是这样的顺序,当然排序的时候会比较1和5,然后先1取出来,在把5取出来,3和8同理,然后就变成了1 5 3 8,然后取出1,这时第一队变成5,第二队变成3 8,然后取出3,取出5,取出8,这样就排好啦。
update:之所以每次只看一半是可行的,是因为前面一半不管内部顺序怎么变,能和后面一半里的数组成逆序对的,变弯还是在那些数的前面,也就是还是可以组成逆序对,后面一半也是如此,而每次合并的时候,由于前后都是已经排好序的(升序),所以当前半部分有一个数a大于后半部分一个数b时,那么a后面的属于前半部分的数都大于b,也就是都可以和b构成逆序对,直接加上这些数的个数就可以了。我纸箱前半部分的指针i和指向后半部分的指针j都是动态的,没次都找出当前最小的元素,然后相应的指针要往后挪,因为如果这个最小元素在前半部分,那么他不会在构成逆序对了,如果他在后半部分,我已经加了前面能和他构成逆序对的数的个数,往后继续找就行了。什么?你问我一个数和自己同属一部分的数构成逆序对的咋办?你忘了归并是分治?我是一直分治到一部分只剩下一个元素才会返回的,这样在他们不属于同意部分的时候我就处理好并把他们排序了。
而求逆序对的原理就是,左右2边排序不影响统计左边的数和右边的数是不是逆序对,所以当每次较小的那个数是右边那队的队首的时候,左边那队的目前的所有数都比他大,所以所有的数都能和它构成逆序对,统计这个数量,最后求出的就是逆序对数。
模板:https://www.luogu.org/problemnew/show/P1908
1 #include<iostream> 2 #include<cstdio> 3 using namespace std; 4 int n; 5 int a[50000],b[50000]; 6 int ans; 7 void msort(int l,int r) 8 { 9 if(l==r)return; 10 int mid=(l+r)>>1; 11 msort(l,mid),msort(mid+1,r); 12 int i=l,j=mid+1,k=l-1; 13 while(i<=mid&&j<=r) 14 { 15 k++; 16 if(a[i]<=a[j])b[k]=a[i],i++; 17 else b[k]=a[j],ans+=mid-i+1,j++;//ans+=mid-i+1就是比归并排序多的那句话 18 } 19 while(i<=mid)b[++k]=a[i],i++; 20 while(j<=r)b[++k]=a[j],j++; 21 for(int i=l;i<=r;i++)a[i]=b[i]; 22 } 23 int main() 24 { 25 scanf("%d",&n); 26 for(int i=1;i<=n;++i)scanf("%d",&a[i]); 27 msort(1,n); 28 cout<<ans<<endl; 29 return 0; 30 }