逆序对

什么是逆序对??

我们在这里给出一个定义:如果i<j&&a[i]>a[j]的一对数称为逆序对。

为什么??!!

背过!!(都说了是定义了!)(其实我也不知道为什么  orz   (*^__^*) ……)

但是知道逆序对有什么用处呢?

  • 具体用处我也不是很清楚,但是目前我们可以用逆序对的个数求将一列数排成有序的一列数交换的次数

怎样求逆序对呢??

我们在这里给出两种算法:归并排序求逆序对;树状数组求逆序对。

          §归并排序求逆序对

归并排序的核心思想是二分。

我们将一个序列a1,a2,a3,a4,a5,a6.........an;进行归并排序

对于归并排序还有童鞋不明白吗?

我们在这里就简单提一下归并排序吧。

说起排序来了,有人肯定又要说,要归并排序干嘛?sort不就挺好的吗?又快有省时。所以有些人就不重视归并排序。但是试问,你的sort可以求逆序对吗?

显然是不能的!所以我们还是要好好学归并排序的(算了,扯多了)

下面我们来说说归并排序吧!

所谓归并排序就是将一组数进行无数次分割,将它划分成一个个有序的区间,然后再将这一个个有序的区间一个个合并成有序的区间。

怎么划分?

那这就是一个二分的过程了。我们每次将一段数进行分开,我们在这里引入一个mid为中间位置,一直将l到mid进行分,知道mid=l,这说明他已经将一个个连续的区间划分成一个个独立的区间了。这样我们在在一个个进行合并的时候由于我们划分成两个区间(即两个两个区间进行合并),并且每一个区间都是有序的。

实际上归并排序的交换次数就是这个数组的逆序对个数,为什么呢?

我们可以这样考虑:

归并排序是将数列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。因此,可以在归并

排序中的合并过程中计算逆序数.

这样对于归并排序求逆序对其实就是在归并排序的基础上加上了一个统计逆序对的过程。

那样如果我们在进行合并的时候找到前一个区间的值比后一个区间的值大,那样是不是就说明从这个数往后的所有数都比这个数小,那样的话就说明从这个数往后的所有数多会与下一个区间的数形成逆序对,那样的话我们在统计逆序对个数的时候就直接将这个区间的之后比这个数大的数的个数。以此类推。。。

下面附上代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
#define N 100001
using namespace std;
int n,a[N];
long long ans;
void gsort(int l,int r)
{
     if(l==r) return ;
    int mid=(l+r)/2;int tmp[N];
    gsort(l,mid),gsort(mid+1,r);
    int i=l,j=mid+1,k=l;
    while(i<=mid&&j<=r)
    {
        if(a[i]<=a[j]) tmp[k++]=a[i++];
        else
        {
            ans+=mid-i+1;
            tmp[k++]=a[j++];
        }
    }
    while(i<=mid) tmp[k++]=a[i++];
    while(j<=r) tmp[k++]=a[j++];
    for(int i=l;i<=r;i++) a[i]=tmp[i];
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    gsort(1,n);
    cout<<ans;
    return 0;
} 

          §树状数组求逆序对

(这种做法我就没打算用)

从大佬博客里粘了个代码,就凑活着看看吧。。。

#include <algorithm>
#include <cstdio>

#define lowbit(x) (x&(-x))

using namespace std;

const int N(40000+15);
int n,x,c[N],ans;
struct Node
{
    int num,mark;
}a[N];

bool cmp(Node a,Node b)
{
    return a.num>b.num;
}

inline void up(int x)
{
    for(;x<=N;x+=lowbit(x)) c[x]++;
}

inline int query(int x)
{
    int ret=0;
    for(;x;x-=lowbit(x)) ret+=c[x];
    return ret;
}

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i].num),a[i].mark=i;
    sort(a+1,a+n+1,cmp);
    for(int i=1;i<=n;i++)
    {

        ans+=query(a[i].mark);
        up(a[i].mark);
    }
    printf("%d",ans);
    return 0;}

大佬博客http://www.cnblogs.com/Shy-key/p/6930563.html

时间: 2024-08-26 08:23:28

逆序对的相关文章

codeforces 414C C. Mashmokh and Reverse Operation(归并排序求逆序对)

题目链接: C. Mashmokh and Reverse Operation time limit per test 4 seconds memory limit per test 512 megabytes input standard input output standard output Mashmokh's boss, Bimokh, didn't like Mashmokh. So he fired him. Mashmokh decided to go to university

求逆序对(线段树版)

一个序列a1,a2,a3...aN,求出满足:ai > aj 且 i < j 的个数. 一个最容易想到的方法就是枚举所有的i,j看看是否满足,显然是O(n^2)的复杂度.不够好. 可以这样考虑,开一个数组保存这n个数出现的位置和对应的次数,这个数组要开到a数组里最大的那个数MAX,也就是hash,初始状态数组里没有元素,每个数对应的个数都是0. 如果考虑第i个数,找到比它大的所有的数 的个数,查找的范围即 ai+1~MAX,这就是到i这个位置的逆序对的总和,接着把a[i]这个数添加到数组里,也

剑指offer: 数组中的逆序对

1. 最简单的思路,对每个值,遍历与其逆序的数组对:但时间复杂度太高: 2. 归并排序的思路: 先将数组分隔成子数组,先统计出子数组内的逆序对的数目,然后统计两个相邻子数组之间的逆序对的数目: int InversePairsCore(int *data, int * copy, int start, int end) { //递归介绍条件,只剩一个元素 if(start==end) { copy[start]=data[start]; return 0; } int length=(end-s

[HAOI2009]逆序对数列

题目描述 对于一个数列{ai},如果有i<j且ai>aj,那么我们称ai与aj为一对逆序对数.若对于任意一个由1~n自然数组成的数列,可以很容易求出有多少个逆序对数.那么逆序对数为k的这样自然数数列到底有多少个? 输入输出格式 输入格式: 第一行为两个整数n,k. 输出格式: 写入一个整数,表示符合条件的数列个数,由于这个数可能很大,你只需输出该数对10000求余数后的结果. 输入输出样例 输入样例#1: 4 1 输出样例#1: 3 说明 样例说明: 下列3个数列逆序对数都为1:分别是1 2

bzoj3295动态逆序对

Cqoi2011]动态逆序对 Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 5362  Solved: 1814[Submit][Status][Discuss] Description 对于序列A,它的逆序对数定义为满足i<j,且Ai>Aj的数对(i,j)的个数.给1到n的一个排列,按照某种顺序依次删除m个元素,你的任务是在每次删除一个元素之前统计整个序列的逆序对数. Input 输入第一行包含两个整数n和m,即初始元素的个数和删除的元素个数

【bzoj3295】动态逆序对

我怎么控制不住自己又写了个数据结构啊--真是的-- 其实我是想练CDQ分治的--结果忍不住又写了个主席树. 首先看看不动态的逆序对咋做?树状数组嘛. 那么删除咋搞?就是考虑贡献,把它前面比他大的,后面比他小的减去-- 诶?带修改主席树?我--我好像才写过--? 1 #include<bits/stdc++.h> 2 #define inf 0x7fffffff 3 #define N 100005 4 #define M 5000005 5 using namespace std; 6 typ

[BZOJ3295][Cqoi2011]动态逆序对

试题描述 对于序列A,它的逆序对数定义为满足i<j,且Ai>Aj的数对(i,j)的个数.给1到n的一个排列,按照某种顺序依次删除m个元素,你的任务是在每次删除一个元素之前统计整个序列的逆序对数. 输入 输入第一行包含两个整数n和m,即初始元素的个数和删除的元素个数.以下n行每行包含一个1到n之间的正整数,即初始排列.以下m行每行一个正整数,依次为每次删除的元素. 输出 输出包含m行,依次为删除每个元素之前,逆序对的个数. 输入示例 5 4 1 5 3 4 2 5 1 4 2 输出示例 5 2

[bzoj2431][HAOI2009][逆序对数列] (dp计数)

Description 对于一个数列{ai},如果有i<j且ai>aj,那么我们称ai与aj为一对逆序对数.若对于任意一个由1~n自然数组成的 数列,可以很容易求出有多少个逆序对数.那么逆序对数为k的这样自然数数列到底有多少个? Input 第一行为两个整数n,k. Output 写入一个整数,表示符合条件的数列个数,由于这个数可能很大,你只需输出该数对10000求余数后的结果. Sample Input 4 1 Sample Output 3 样例说明: 下列3个数列逆序对数都为1:分别是1

bzoj 3295 动态逆序对 CDQ分支

容易看出ans[i]=ans[i-1]-q[i],q[i]为删去第i个数减少的逆序对. 先用树状数组算出最开始的逆序对,预处理出每个数前边比它大的和后边比它小的,就求出了q[i]的初始值. 设b[i]是第i个删除的数,pos[i]为i在数列里的位置. 对q[i]产生影响的是   1. j<i,pos[b[j]]<pos[b[i]],b[j]>b[i].  2.j<i,pos[b[j]]>pos[b[i]],b[j]<b[i]. 因为是三维的,所以我们套一层cdq. 1

【bzoj3295】 Cqoi2011—动态逆序对

http://www.lydsy.com/JudgeOnline/problem.php?id=3295 (题目链接) 题意 给出某种排列,按照某种顺序依次删除m个数,在每次删除一个数前统计序列中逆序对对个数. Solution 作为一个CDQ分治的初学者,我毫不犹豫的%了LCF的题解. 这里介绍下三维偏序的求法:一维排序,二维归并,三维树状数组. 排序维护x维之后,递归处理: 1.在处理区间[L,R]的时候,先二分区间[L, (L+R)/ 2],递归求这个左区间(二分的原因是我在维护y维的时候