逆序对(归并排序+树状数组)

题目描述

猫猫 TOM 和小老鼠 JERRY 最近又较量上了,但是毕竟都是成年人,他们已经不喜欢再玩那种你追我赶的游戏,现在他们喜欢玩统计。

最近,TOM 老猫查阅到一个人类称之为“逆序对”的东西,这东西是这样定义的:对于给定的一段正整数序列,逆序对就是序列中 a_i>a_j且 i<j的有序对。知道这概念后,他们就比赛谁先算出给定的一段正整数序列中逆序对的数目。注意序列中可能有重复数字。

输入格式

第一行,一个数 n,表示序列中有 n个数。

第二行 n 个数,表示给定的序列。序列中每个数字不超过 10^9。

输出格式

输出序列中逆序对的数目。

输入输出样例

输入 

6
5 4 2 6 3 1

输出 

11

说明/提示

对于 25% 的数据,n≤2500

对于 50% 的数据,n≤4×104。

对于所有数据,n≤5×105

 Solution#1 归并排序MergeSort

对于归并排序的原理解释指路归并排序https://www.iamshuaidi.com/549.htmlicon-default.png?t=M276https://www.iamshuaidi.com/549.html

为什么归并排序可以用来求逆序对?

  • 归并排序利用分治思想,先把原始数组不断“分”,分成只有一个元素的“有序”序列,再把相邻两个数组不断合并也就是“治”,再对任意两个有序序列合并时,在判断大小时只要多加一行代码判断此时指标 j 指向的右边数组里的元素是不是小于指标 i 指向左边数组里的元素,如果是那么 i 到左边数组的最后一个都会比 j 指向的元素大,i~最后一个元素之间的所有元素都和j是逆序对,那么就把这之间的个数加起来就是最终答案
  • 归并排序是稳定的,相等的元素位置不会改变

ACCODE

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define maxn 500000+5
int a[maxn], tmp[maxn];
ll cnt;

void merge(int left, int center, int right)
{
    int i = left, j = center + 1;
    for (int k = left; k <= right; k++)
    {
        if (i > center)
            tmp[k] = a[j++];
        else if (j > right)
            tmp[k] = a[i++];
        else if (a[i] <= a[j])
            tmp[k] = a[i++];
        else
        {
            /* Hi!添加的代码在这里!*/
            cnt += (center + 1 - i);
            tmp[k] = a[j++];
        }
    }
    for (int k = left; k <= right; k++)
        a[k] = tmp[k];
}
void mergesort(int left, int right)
{
    if (left < right)
    {
        //center指向左边序列的最后一个的下标
        int center = (left + right) / 2;
        mergesort(left, center);
        mergesort(center + 1, right);
        merge(left, center, right);
    }
}
int main()
{
    int n;
    cin >> n;
    for (int i = 0; i < n; i++)
        cin >> a[i];
    mergesort(0, n - 1);
    cout << cnt;
    return 0;
}

 Solution#2 树状数组(Binary Indexed Tree)

 对于树状数组的原理解释指路树状数组https://blog.csdn.net/bestsort/article/details/80796531?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522163954974116780265449393%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=163954974116780265449393&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~top_positive~default-1-80796531.pc_search_result_control_group&utm_term=%E6%A0%91%E7%8A%B6%E6%95%B0%E7%BB%84&spm=1018.2226.3001.4187icon-default.png?t=M276https://blog.csdn.net/bestsort/article/details/80796531?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522163954974116780265449393%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=163954974116780265449393&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~top_positive~default-1-80796531.pc_search_result_control_group&utm_term=%E6%A0%91%E7%8A%B6%E6%95%B0%E7%BB%84&spm=1018.2226.3001.4187

补充说明:

树状数组是通过二进制和lowbit函数来实现一种访问方式来把数组模拟成一颗树

每个结点放的这个结点和其所有子节点的值的和,用tree数组表示树状数组,对每个结点的下标i+lowbit(i)得到的值都是其父节点(原理请看链接),这样在更新一个结点时,就通过这种方式来找到其所有影响到1父节点一一更新
例如:给结点5加1,那么tree[5]+1,其父节点=(101 + 1)二进制=110 二进制=6 十进制同理跟新110+10 = 1000= 8结点,1000+1000=10000=16结点.....都加上1,维护区间平衡
在查询求和时,每个结点的小标i-lowbit(i) 都能得到和的另一部分
例如:求前六项和,如下图可以看到sum(6) = t[6] + t[4] ( t[4]包括了t[3]+t[2]+t[1] ) ,而 6 = 0b110,6-lowbit(6) = 0b110 - 0b010 = 0b100 = 4,得到的4刚好就是求和中的另一部分

 

 在求逆序对时的其他操作:

  • 用a数组存储原始的元素的同时,用p数组存储元素的下标位置,最后再把p数组按照a数组的大小进行排序,这就是对原始数组的离散化操作,例如3个数字1,10000,2,87777经过离散化后的p数组为{1,3,2,4},其中的10000被修改成立3,能很大程度的节省树状数组空间(不然就要开到87777)
  • 从p数组第一个开始遍历,每遍历一个元素,就在t数组中相应的位置+1,例如遍历到3,那么t[3]+=1,t[4]+=1,表示在此时小于等于3和小于等于4的元素又出现了一个,updata后就开始query,如果此时小于等于3的个数不足3个,表示还有比3小的数字在后面,这就是逆序对,每个元素的逆序对个数为cnt = i - query(i),差值就是还有几个在后面,就是逆序对。累加求和就是答案

ACCODE

#include <bits/stdc++.h>
using namespace std;
#define maxn 500000 + 5
typedef long long ll;
int t[maxn], a[maxn], p[maxn];
int n;
ll ans;
int lowbit(int x)
{
    return x & (-x);
}

void update(int x)
{
    while (x <= n)
    {
        t[x] += 1;
        x += lowbit(x);
    }
}

ll query(int x)
{
    ll sum = 0;
    while (x)
    {
        sum += t[x];
        x -= lowbit(x);
    }
    return sum;
}

bool cmp(int i, int j)
{
    return a[i] < a[j];
}
int main()
{
    cin >> n;
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
        p[i] = i;
    }
    stable_sort(p + 1, p + n + 1, cmp);
    for (int i = 1; i <= n; i++)
    {
        update(p[i]);
        ans += i - query(p[i]);
    }
    cout << ans;
    return 0;
}

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

linengcs

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值