• 树状数组 && 线段树应用 -- 求逆序数


    参考:算法学习(二)——树状数组求逆序数 、线段树或树状数组求逆序数(附例题)

    应用树状数组 || 线段树求逆序数是一种很巧妙的技巧,这个技巧的关键在于如何把原来单纯的求区间和操作转换为 求小于等于a的数的总数 再转换为 求序列里大于a的数的总数,学习这个技巧源于一道题目 poj 3067 Japan (一道需要YY后运用这个技巧求解的题目),此外这个技巧也让我联想到 树状数组区间加/单点求值的技巧(基于区间加法的思维),话不多说,开始正题。

    一、什么是逆序数?

      在一个排列中,如果一对数的前后位置与大小顺序相反,即前面的数大于后面的数,那么它们就称为一个逆序。一个排列中逆序的总数就称为这个排列的逆序数。一个排列中所有逆序总数叫做这个排列的逆序数。也就是说,对于n个不同的元素,先规定各元素之间有一个标准次序(例如n个 不同的自然数,可规定从小到大为标准次序),于是在这n个元素的任一排列中,当某两个元素的先后次序与标准次序不同时,就说有1个逆序。一个排列中所有逆序总数叫做这个排列的逆序数。

      举个栗子:如2431中,21,43,41,31是逆序,逆序数是4。

    二、如何用树状数组求逆序数?

    1、定义: a[ i ] 储存原始序列(事实上只需要一个变量 a 就ok了), i 即 出现的顺序;c[ k ] 树状数组 k 为 a[ i ]。

    2、步骤:按顺序输入储存原始序列 a[ i ] , 输入的同时维护 c[ i ] , a[i] 出现 c[ a[i] ] 则加一, add(a[ i ], 1);所以用数组数组求(1, a[i]) 的区间和的意义就变成了统计小于等于 a[ i ] 的数的个数, 若当前序列总数为 N, 则 N - sum( a ) 就是长度为N的序列中比 a 大的数字的总数了。

    3、举个栗子:2431   ans = 0 + 0 + 1 + 3 = 4;   分别是(21, 43, 41, 31)。

    i a[ i ] k c[ 1 ] ~ c[ 4 ]  i - sum( k )
    1 2 2 0 1 0 0 1 - 1 = 0
    2 4 4 0 1 0 1 2 - 2 = 0
    3 3 3 0 1 1 1 3 - 2 = 1
    4 1 1 1 1 1 1 4 - 1 = 3

                  

    4、贴个代码:

     1 ///树状数组求逆序数
     2 #include <cstdio>
     3 #include <cstring>
     4 #include <iostream>
     5 #include <algorithm>
     6 using namespace std;
     7 
     8 const int MAXN = 1001;
     9 int c[MAXN];
    10 int N;
    11 
    12 int lowbit(int x) ///实现树状数组需要的基本函数
    13 {
    14     return x&(-x);
    15 }
    16 
    17 void add(int i, int value)
    18 {
    19     while(i <= N)
    20     {
    21         c[i]+=value;
    22         i+=lowbit(i);
    23     }
    24 }
    25 
    26 int sum(int i)
    27 {
    28     int res = 0;
    29     while(i > 0)
    30     {
    31         res+=c[i];
    32         i-=lowbit(i);
    33     }
    34     return res;
    35 }
    36 int main()
    37 {
    38     while(~scanf("%d", &N))
    39     {
    40         int ans = 0;
    41         memset(c, 0, sizeof(c));
    42         for(int i = 1; i <= N; i++)
    43         {
    44             int a;
    45             scanf("%d", &a);
    46             add(a, 1);
    47             ans+=i-sum(a);
    48         }
    49         printf("%d
    ", ans);
    50     }
    51     return 0;
    52 }

    三、如何用线段树求逆序数

    与树状数组原理,只不过实现的区间求和的方式不同罢了

    直接贴代码:

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #define L(a) a<<1
    #define R(a) (a<<1)|1
    const int maxn = 51000;
    int ans[maxn];
    struct node{
        int num,l,r;
    }tree[maxn<<2];
    int n;
    void Build(int m,int l, int r){
        tree[m].l=l;
        tree[m].r=r;
        if(tree[m].l==tree[m].r){
            tree[m].num=0;
            return ;
        }
        int mid = (tree[m].l+tree[m].r)>>1;
        Build(L(m),l,mid);
        Build(R(m),mid+1,r); //并不要回溯, 建立空树
    }
    void Insert(int m,int l,int r,int x){
        if(tree[m].l==l&&tree[m].r==r){
            tree[m].num+=x; return ;
        }
        int mid = (tree[m].l+tree[m].r)>>1;
        if(r<=mid)
            Insert(L(m),l,r,x);
        else if(l>mid)
            Insert(R(m),l,r,x);
        else{
            Insert(L(m),l,mid,x);
            Insert(R(m),mid+1,r,x);
        }
        tree[m].num=tree[L(m)].num+tree[R(m)].num;
    }
    int Query(int m,int l,int r){
        if(tree[m].l==l&&tree[m].r==r)
            return tree[m].num;
        int mid = (tree[m].l+tree[m].r)>>1;
        if(r<=mid)
            return Query(L(m),l,r);
        if(l>mid)
            return Query(R(m),l,r);
        return Query(L(m),l,mid)+Query(R(m),mid+1,r);
    }
    int main(){
        int a,n,i,t;
    
            int k=0;
            scanf("%d",&n);
            memset(tree,0,sizeof(tree));
            Build(1,1,n);
            for(int i=1;i<=n;i++)
            {
                scanf("%d",&ans[i]);
            }
            for(int i=1;i<=n;i++){
                Insert(1,ans[i],ans[i],1);// 每个位置插入1
                k+=(i - Query(1,1,ans[i]));
            }
            printf("%d
    ",k);
    
        return 0;
    }

    四、几道题目

    1、HDU 1394

    思路:树状数组+暴力(每次a[ i ] 掉到最后的时候 减去比 a[ i ] 小的数, 加上比 a[ i ] 大的数)

    Ac Code:

     1 ///HDU 1394 树状数组
     2 
     3 #include <cstdio>
     4 #include <cstring>
     5 #include <iostream>
     6 #include <algorithm>
     7 #define INF 0x3f3f3f3f
     8 using namespace std;
     9 
    10 const int MAXN = 5005;
    11 
    12 int N;
    13 int c[MAXN], a[MAXN];
    14 
    15 int lowbit(int x)
    16 {
    17     return x&(-x);
    18 }
    19 
    20 void add(int i, int value)
    21 {
    22     while(i <= N)
    23     {
    24         c[i]+=value;
    25         i+=lowbit(i);
    26     }
    27 }
    28 
    29 int sum(int i)
    30 {
    31     int res = 0;
    32     while(i > 0)
    33     {
    34         res+=c[i];
    35         i-=lowbit(i);
    36     }
    37     //printf("%d
    " ,res);
    38     return res;
    39 }
    40 
    41 int main()
    42 {
    43     int cnt = 0;
    44     while(~scanf("%d", &N))
    45     {
    46         cnt = 0;
    47         memset(c, 0, sizeof(c));
    48         memset(a, 0, sizeof(a));
    49         for(int i = 1; i <= N; i++)
    50         {
    51             scanf("%d", &a[i]);
    52             a[i]++;
    53             add(a[i], 1);
    54             cnt+=i-sum(a[i]);
    55         }
    56 
    57         int ans = cnt;
    58         //printf("%d
    ", ans);
    59         for(int i = 1; i <= N; i++)
    60         {
    61             a[i]--;
    62             cnt = cnt-a[i]*2+N-1;
    63             ans = min(ans, cnt);
    64             //printf("%d
    ", ans);
    65         }
    66         printf("%d
    ", ans);
    67     }
    68     return 0;
    69 }
    View Code
  • 相关阅读:
    oracle的over函数应用(转载)
    Oracle decode()函数应用
    EL表达式显示数据取整问题
    null值与空值比较
    case when语句的应用
    堆排序
    希尔排序
    插入排序
    异或运算
    选择排序
  • 原文地址:https://www.cnblogs.com/ymzjj/p/9370598.html
Copyright © 2020-2023  润新知