浅谈求静态逆序对

  静态求逆序对就是没有修改的序列求逆序对,如果有修改就要用cdq了Orz

  静态求逆序对的方法本弱只会2种,归并排序求逆序对和树状数组求逆序对。

  归并排序求逆序对

  代码只比普通归并排序多了一句话。归并排序是一直分治下去,直到分到一个小区间里只有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,这样就排好啦。

  而求逆序对的原理就是,左右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

 

转载于:https://www.cnblogs.com/yuelian/p/8909453.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值