• 浅谈如何求逆序对


    浅谈求逆序对的两种方式

    本篇博客讲解一下信息学奥林匹克竞赛中一种小技巧(我也不知道这是算法还是技巧)——求逆序对


    逆序对的概念

    先放一波啥是逆序对...

    对于一个数列(a),假如(a[i]>a[j])并且(i<j),那么这个(a[i],a[j])就叫做这个数列的一个逆序对。

    简单理解一下:假如本来这个数列是单调递增的,突然出来了一对不和谐的,它非要皮一下,两个数调换一下位置。那么这个不和谐的数对就叫做逆序对。


    归并排序求逆序对

    归并排序是求逆序对的一个常见并好用的手段。

    如果又小可爱不太了解归并排序,那么请翻阅我的这篇博客:

    归并排序详解

    其实这篇博客已经讲了咋用归并排序求逆序对...就是我下面讲的那些(Ctrl CV大法)

    首先来放结论:如果在归并排序过程中,出现(a[i]>a[j]),那么就会产生(mid-i+1)个逆序对。

    很奇妙吧?下面来讲证明:

    因为我们做归并排序是用到了分治的思想,最后的操作其实就是递归回溯,从小到大地合并,所以这个时候,我们的两个子序列(即(l-mid)(mid+1-r)其实都是已经排好序的),这个时候,出现了一个不和谐的(a[i]),说明从这个数一直到(a[mid])的所有数都是不和谐的。我们直接累加就好。

    代码只需要在模板归并排序的基础上再加一行即可:(已经用(Attention)标明)

    void merge_sort(int l,int r)
    {
        if(l==r)
            return;
        int mid=(l+r)>>1;
        merge_sort(l,mid);
        merge_sort(mid+1,r);
        int i=l,j=mid+1,k=l;
        while(i<=mid && j<=r)
        {
            if(a[i]<=a[j]) 
                b[k++]=a[i++];
            else
            {
                b[k++]=a[j++];
                ans+=mid-i+1;//Attention
            }
        }
        while(i<=mid)
            b[k++]=a[i++];
        while(j<=r)
            b[k++]=a[j++];
        for(int p=l;p<=r;p++)
            a[p]=b[p],b[p]=0;
    }
    

    树状数组求逆序对

    使用树状数组求逆序对的想法可能更加的抽象一些。

    关于树状数组,如果有需要补习的小伙伴,推荐自己的这篇博客:

    树状数组知识点详解

    首先,我们需要明白,为什么逆序对可以使用树状数组来求:其实,根据逆序对的定义,我们会发现:其实求解逆序对的过程就是在找一个序列中,在一个数的前面有多少比它大的数。那么,这种区间统计的题,完全可以使用树状数组这种数据结构来解决。

    树状数组的功能就是单点修改,区间查询。那么,我们开一个树状数组

    (注意:这里的数据可能需要进行离散化,当数据量过大的时候,需要把区间大小离散成元素数量,如有离散化不明白的小伙伴,可以看我的这篇博客:)

    浅谈离散化

    这个树状数组表示的是在当前元素前且比当前元素大的元素个数。我们从头开始扫,每碰到一个数,就用树状数组的修改操作向上修改,加1。我们查询的时候就可以知道有多少个数比它大,然后用现在已经插入的元素个数减去查询的数量(包括它自己),就可以得出当前逆序对的个数。

    模板大致如下:(JDOJ 1927 求逆序对

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    using namespace std;
    int c[500010],rk[500010],n;
    long long ans; 
    struct point
    {
        int num,val;
    }a[500010];
    inline bool cmp(point q,point w)
    {
        if(q.val==w.val)
            return q.num<w.num;
        return q.val<w.val;
    }
    inline void fix(int p,int d)
    {
        for(;p<=n;p+=p&-p)
            c[p]+=d; 
    }
    inline int getsum(int x)
    {
        int sum=0;
        for(;x;x-=x&-x)
            sum+=c[x];
        return sum;
    }
    int main()
    {
        scanf("%d",&n);
        for(int i=1;i<=n;i++)
            scanf("%d",&a[i].val),a[i].num=i;
        sort(a+1,a+1+n,cmp);
        for(int i=1;i<=n;i++)
            rk[a[i].num]=i;
        for(int i=1;i<=n;i++)
        {
            fix(rk[i],1);
            ans+=i-getsum(rk[i]);
        }
        printf("%lld",ans);
        return 0;
    }
    
  • 相关阅读:
    给列表单元格加背景色
    Log4j最简入门及实例
    MySQL 按指定字段自定义列表排序
    MySQL逗号分割字段的列转行
    利用MySQL统计一列中不同值的数量方法示例
    使用docx4j编程式地创建复杂的Word(.docx)文档
    向Docx4j生成的word文档中添加布局--第二部分
    向Docx4j生成的word文档添加图片和布局--第一部分
    使用Docx4j创建word文档
    [简单]docx4j常用方法小结
  • 原文地址:https://www.cnblogs.com/fusiwei/p/11656303.html
Copyright © 2020-2023  润新知