zoukankan      html  css  js  c++  java
  • 逆序数

    1.定义

    在一个排列中,如果一对数的前后位置与大小顺序相反,即前面的数大于后面的数,那么它们就称为一个逆序。一个排列中逆序的总数就称为这个排列的逆序数

    举个例子:

    标准列是1 2 3 4 5

    那么 5 4 3 2 1 的逆序数算法:

    看第二个,4之前有一个5,在标准列中5在4的后面,所以记1个 类似的,

    第三个 3 之前有 4 5 都是在标准列中3的后面,所以记2个 同样的,

    2 之前有3个,

    1之前有4个

    将这些数加起来就是逆序数=1+2+3+4=10

    再举一个 2 4 3 1 5

    4 之前有0个

    3 之前有1个

    1 之前有3个

    5 之前有0个

    所以逆序数就是1+3=4

    2.求法

    1.朴素方法,两层循环,时间复杂度(O (n^2))

     1 int count=0;
     2 for(i=0; i<n-1; i++)
     3 {
     4     for(j=i+1; j<n; j++)
     5     {
     6         if(a[i]>a[j])
     7         {
     8             count++;
     9         }
    10     }
    11 }

     2.归并排序,时间复杂度(O(n log n))

    归并排序是将数列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。因此,可以在归并排序中的合并过程中计算逆序数.

    现在以6 1 7 2为例,我们以7 2这一块来说,归并排序中当7进入临时空间的时候,看看6 1这一块还剩下几个元素没有入临时空间,剩下的元素必定比7大,剩下的元素个数就是由于7产生的逆序对数目,同样地1产生的逆序对数目类似统计,同样,由于不同块之间互不影响,递归解决此问题。说的有点乱,但仔细想想是这个道理!!!!!

     1 #include<cstdio>
     2 #include<cstring>
     3 #include<algorithm>
     4 #define maxn 500010
     5 #define ll long long int
     6 using namespace std;
     7 ll a[maxn];
     8 ll temp[maxn];
     9 ll sum;
    10 void Merge(int l,int r,int m)
    11 {
    12     int i=l;
    13     int j = m + 1;
    14     int k = l;
    15     while(i<=m&&j<=r)
    16     {
    17         if(a[i]>a[j])
    18         {
    19             sum+=m-i+1;///剩下的没有进入临时空间的元素的个数
    20             temp[k++]=a[j++];
    21         }
    22         else
    23         {
    24             temp[k++]=a[i++];
    25         }
    26     }
    27     while(i<=m)///将剩余的元素存到数组中
    28     {
    29         temp[k++]=a[i++];
    30     }
    31     while(j<=r)
    32     {
    33         temp[k++]=a[j++];
    34     }
    35     for(i=l; i<=r; i++)
    36     {
    37         a[i]=temp[i];
    38     }
    39 }
    40 void mergesort(int l,int r)
    41 {
    42     if(l<r)
    43     {
    44         int m = (l + r) / 2;
    45         mergesort(l,m);///左二分排序
    46         mergesort(m+1,r);///右二分排序
    47         Merge(l,r,m);///合并两个升序数组
    48     }
    49 }
    50 int main()
    51 {
    52     int n,i;
    53     while(scanf("%d",&n)!=EOF)
    54     {
    55         if(n==0)
    56         {
    57             break;
    58         }
    59         for(i=0; i<n; i++)
    60         {
    61             scanf("%lld",&a[i]);
    62         }
    63         sum=0;
    64         mergesort(0,n-1);
    65         printf("%lld ",sum);
    66     }
    67     return 0;
    68 }

    3.树状数组

    由于树状数组的特性,求和是从当前节点往前求,所以,这里要查询插入当前数值之时,要统计有多少个小于该数值的数还没插入,这些没插入的数,都会在后面插入,也就形成了逆序数。

    假设我们将 序列 6 1 2 7 3 4 8 5 存入数组a【】 中, a【1】=6 , a【2】=1...

    那么每次,我们可以将 a【i】 插入到 树状数组中,并赋值为 1, 我们求和sum,sum 是1 到 a【i】的和 , 那么这个 sum 表示的值就是当前比a【i】小的数量(包括它本身);而当前一共有 i 个数 , 所以 当前 比a【i】大的数量就是 i - sum;所以 我们统计所有的 i - sum , 它们的和就是逆序数。

     1 #include <iostream>
     2 #include <cstdio>
     3 #include <cstring>
     4 #include <algorithm>
     5 using namespace std;
     6 #define LL long long
     7 #define N 1005*1005
     8 LL ans;
     9 int a[N];
    10 int n,c[N];
    11 int lowbit(int x)
    12 {
    13     return x&-x;
    14 }
    15 int Getsum(int x)
    16 {
    17     int ret = 0;
    18     while(x>0)
    19     {
    20         ret+=c[x];
    21         x-=lowbit(x);
    22     }
    23     return ret;
    24 }
    25 
    26 void add(int x,int d)
    27 {
    28     while(x<=n)
    29     {
    30         c[x]+=d;
    31         x+=lowbit(x);
    32     }
    33 }
    34 int main()
    35 {
    36     int i,j,k,l,r,t;
    37     scanf("%d",&n);
    38     memset(c,0,sizeof(c));
    39     for(i = 1; i<=n; i++)
    40     {
    41         scanf("%d",&a[i]);
    42     }
    43     ans = 0;
    44     for(i = 1; i<=n; i++)
    45     {
    46         add(a[i],1);///这里将从c[i]赋值为1更像是一种存在,1代表着存在,0不存在
    47         ans+=i-Getsum(a[i]);
    48     }
    49     printf("%lld
    ",ans);
    50     return 0;
    51 }


    4.线段树

    用线段树来求逆序数的思路关键在于,线段树是维护一个区间的,所以,对于这种连续区间求逆序数,完全可以判断当插入一个新的数字时,若比它大的数字已经插入了,说明排在了它的前面,也就是产生了这些逆序数。其实线段树与树状数组只是两种不同的数据结构,但在逆序对的处理上二者其实是相同的,树状数组有Getsum()函数求1 到 a【i】的和,而线段树则可以使用Query()来查询。

     1 #include <stdio.h>
     2 #include <string.h>
     3 #include <stdlib.h>
     4 #define MAX 51000
     5 #define MID(a,b) (a+b)>>1
     6 #define R(a) (a<<1|1)
     7 #define L(a) a<<1
     8 typedef struct
     9 {
    10     int num,left,right;
    11 } Node;
    12 int ans[MAX];
    13 Node Tree[MAX<<2];
    14 int n;
    15 void Build(int t,int l,int r)         //以1为根节点建立线段树
    16 {
    17     int mid;
    18     Tree[t].left=l,Tree[t].right=r;
    19     if(Tree[t].left==Tree[t].right)
    20     {
    21         Tree[t].num=0;
    22         return ;
    23     }
    24     mid=MID(Tree[t].left,Tree[t].right);
    25     Build(L(t),l,mid);
    26     Build(R(t),mid+1,r);
    27 }
    28 
    29 void Insert(int t,int l,int r,int x)     //向以1为根节点的区间[l,r]插入数字1
    30 {
    31     int mid;
    32     if(Tree[t].left==l&&Tree[t].right==r)
    33     {
    34         Tree[t].num+=x;
    35         return ;
    36     }
    37     mid=MID(Tree[t].left,Tree[t].right);
    38     if(l>mid)
    39     {
    40         Insert(R(t),l,r,x);
    41     }
    42     else if(r<=mid)
    43     {
    44         Insert(L(t),l,r,x);
    45     }
    46     else
    47     {
    48         Insert(L(t),l,mid,x);
    49         Insert(R(t),mid+1,r,x);
    50     }
    51     Tree[t].num=Tree[L(t)].num+Tree[R(t)].num;
    52 }
    53 int Query(int t,int l,int r)           //查询以1为根节点,区间[l,r]的和
    54 {
    55     int mid;
    56     if(Tree[t].left==l&&Tree[t].right==r)
    57         return Tree[t].num;
    58     mid=MID(Tree[t].left,Tree[t].right);
    59     if(l>mid)
    60     {
    61         return Query(R(t),l,r);
    62     }
    63     else if(r<=mid)
    64     {
    65         return Query(L(t),l,r);
    66     }
    67     else
    68     {
    69         return Query(L(t),l,mid)+Query(R(t),mid+1,r);
    70     }
    71 }
    72 int main()
    73 {
    74     int a,n,i,t;
    75     scanf("%d",&t);
    76     long long int k;
    77     while(t--)
    78     {
    79         scanf("%d",&n);
    80         memset(Tree,0,sizeof(Tree));  //初始化线段树
    81         Build(1,1,n);
    82         for(i=1; i<=n; i++)           //输入n个数
    83         {
    84             scanf("%d",&ans[i]);
    85         }
    86         for(i=1,k=0; i<=n; i++)
    87         {
    88             a=ans[i];
    89             Insert(1,a,a,1);          //把线段树[ans[i],ans[i]]区间的值插入为1
    90             k=k+(i-Query(1,1,a));     //查询区间[1,ans[i]]值的总和,逆序数等于i-[1,ans[i]]
    91         }
    92         printf("%lld
    ",k);
    93     }
    94     return 0;
    95 }
  • 相关阅读:
    测试用例编写方法
    mysql数据库在windows下安装与配置
    jQuery文字特效制作文字鼠标滑过多彩色变色显示
    基于浏览器的定位于搜索
    对不起,我又忍不住想你了
    痴语片言
    JQuery-Ajax后台提交数据与获取数据
    $.getJSON异步请求和同步请求
    七、多线程
    硬币收集问题--动态规划3
  • 原文地址:https://www.cnblogs.com/wkfvawl/p/9512861.html
Copyright © 2011-2022 走看看