zoukankan      html  css  js  c++  java
  • 浅谈求静态逆序对

      静态求逆序对就是没有修改的序列求逆序对,如果有修改就要用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 }
    View Code

      树状数组求逆序对数

      树状数组不会的童鞋可以看看这个比较基础的讲解:http://www.cnblogs.com/yuelian/p/8757091.html

      树状数组求逆序对是开一个桶一样的树状数组(是说数据范围很大的时候需要离散化),把每一个数一次加入树状数组中,加之前先询问一下比这个数小的已经在树状数组中的数有多少个,然后用这个数的下标减一在减去询问那得答案就是这个数前面的和这个数构成逆序对的数的个数。其实这么做的暴力一点的做法就是,开一个桶,for一遍,把每个数加到桶里,然后统计当前在桶里的比这个数大的数的个数,累加起来就是总的逆序对数,用树状数组是优化了时间和空间复杂度。而答案加的是当前数的下标-在树状数组里的小于等于当前数的数的个数是因为比这个数先加进去的一定是位置在这个数的前面,而统计出不比这个数大的,用这个数的下标减一减就是在这个数之前并且比这个数大的数的个数,也就是在这个数前面并且和这个数构成逆序对的数的个数,这样for一遍就可以统计出所有的逆序对了。

      还用上面的模板题测试代码就好了

     1 #include<algorithm>
     2 #include<iostream>
     3 #include<cstdio>
     4 using namespace std;
     5 const int maxn=40004;
     6 int n;
     7 int    ans;
     8 int tree[maxn];
     9 int a[maxn],b[maxn];
    10 void add(int x)
    11 {
    12     for(int i=x;i<=40000;i+=(i&(-i)))
    13         tree[i]++;
    14 }
    15 int query(int x)
    16 {
    17     int ans=0;
    18     for(int i=x;i>=1;i-=(i&(-i)))
    19         ans+=tree[i];
    20     return ans;
    21 }
    22 int main()
    23 {
    24     scanf("%d",&n);
    25     for(int i=1;i<=n;++i)scanf("%d",&a[i]),b[i]=a[i];
    26     //离散化 
    27     sort(b+1,b+n+1);
    28     int tmp=unique(b+1,b+n+1)-b;
    29     for(int i=1;i<=n;++i)a[i]=lower_bound(b+1,b+n+1,a[i])-b;
    30     
    31     for(int i=1;i<=n;++i)
    32     {
    33         add(a[i]);
    34         ans+=i-query(a[i]);
    35     }
    36     printf("%d",ans);
    37     return 0;
    38 }
    View Code
  • 相关阅读:
    相机篇
    ValueAnimator动画跳过中间过程的问题
    android 双向文字问题
    android让xml布局的底部跟随软键盘
    给fragment设置进入和退出动画
    android布局控件的LayoutParams
    注意点
    关于与条件判断中的顺序
    栈的反转
    从尾到头打印链表
  • 原文地址:https://www.cnblogs.com/yuelian/p/8909453.html
Copyright © 2011-2022 走看看