Luogu P1908: 逆序对 [树状数组]

题目描述

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

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

输入格式

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

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

输出格式

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

样例 #1

样例输入 #1

6
5 4 2 6 3 1

样例输出 #1

11

思路

这里用到了一个离散化的思想:假设原本的数组是这个样子的{1, 2, 5, 900},我们要对每个元素出现的次数进行计数,那我们就需要开长度为900的数组,但是我们求解逆序对的时候并不需要知道每个数字的具体大小,只要知道他们之间的相对大小关系即可。

所以我们用一个数组d来存放这种相对关系:d[i]代表原数组中第i小的元素的下标。这样就把元素的相对大小的信息存储下来了。

接下来我们就要利用这个d数组来求解原数组中的逆序对的个数了,仔细思考一下,发现原数组中逆序对的个数就是d数组中正序对的个数。

如何利用树状数组来求解答案呢?此时树状数组维护的信息是第[1, x]小的数字的个数。那么我们把d数组中的元素依次加入树状数组中,每次插入时记录下来getsum(d[i]-1)的值,这些值的累加就是答案。

代码

#include <cstdio>
#include <algorithm>

using namespace std;
typedef long long ll;
const int maxn = 5e5+5;
int a[maxn], d[maxn], c[maxn];
int n;

int lowbit(int x) {
    return x&(-x);
}
void add(int i) {
    while(i <= n) {
        ++c[i];
        i += lowbit(i);
    }
}
ll getsum(int i) {
    ll res = 0;
    while(i > 0) {
        res += c[i];
        i -= lowbit(i);
    }
    return res;
}

bool cmp(int i, int j) {
    if(a[i] == a[j]) return i > j; // 当原数组中出现相同的元素时,我们把d数组中较大的下标排在前面,防止相同的元素也被记到逆序对中
    return a[i]>a[j];
}

int main(void)
{
    scanf("%d", &n);
    for(int i = 1; i <= n; ++i) {
        scanf("%d", &a[i]);
        d[i] = i;
    }
    stable_sort(d+1, d+n+1, cmp);
    ll ans = 0;
    for(int i = 1; i <= n; ++i) {
        add(d[i]);
        ans += getsum(d[i]-1);
    }
    printf("%lld\n", ans);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值