洛谷P1908 逆序对 [树状数组解法]

题目传送门

首先明白一个概念叫做离散化

在上面介绍的树状数组中,只需要开一个与原序列中最大元素相等的长度数组就行,那么如果我的序列是\(1,5,3,8,999\),本来\(5\)个元素,却需要开到\(999\)这么大,造成了巨大的空间浪费。

离散化就是另开一个数组\(d\)\(d[i]\)用来存放第\(i\)大的数在原序列的什么位置,比如原序列\(a={5,3,4,2,1}\),第一大就是\(5\),他在\(a\)中的位是\(1\),所以\(d[1]=1\),同理\(d[2]=3\),········所以\(d\)数组为\({1,3,2,4,5}\),转换之后,空间复杂度就没这么高了,但不是求\(d\)中的逆序对了,而是求\(d\)中的正序对,来看一下怎么求的:

首先把\(1\)放到树状数组\(t\)中,此时\(t\)只有一个数\(1\)\(t\)中比\(1\)小的数没有,sum+=0

再把\(3\)放到树状数组\(t\)中,此时\(t\)只有两个数\(1,3\),比\(3\)小的数只有一个,sum+=1

\(2\)放到树状数组\(t\)中,此时\(t\)只有三个数\(1,2,3\),比\(2\)小的数只有一个,sum+=1

\(4\)放到树状数组\(t\)中,此时\(t\)只有四个数\(1,2,3,4\),比\(4\)小的数有三个,sum+=3

\(5\)放到树状数组\(t\)中,此时\(t\)只有五个数\(1,2,3,4,5\),比\(5\)小的数有四个,sum+=4

最后算出来,总共有\(9\)个逆序对,可以手算一下原序列\(a\),也是\(9\)个逆序对,

具体实现:

  • \(d[1]=1, d[2]=2········a[n]=n\)

  • 根据原数组\(a\)中的元素的大小进行排序

二、黄海修改后的代码

#include <bits/stdc++.h>
using namespace std;
const int N = 500010;
typedef long long LL;
#define lowbit(x) (x & -x)
LL ans;
int n;

//每个输入的数字,我们关心两个方面:1、数值 2、位置(序号)
struct Node {
    int val;
    int id;
} d[N];

bool cmp(Node a, Node b) {
    if (a.val == b.val) return a.id > b.id; //两个数一样大,序号大的靠前,这样统计出来的正序对才正确
    return a.val > b.val;                   //数不一样大,数大的靠前
}

//下面是树状数组模板
int t[N];

//将序列中第x个数加上k
void add(int x, int k) {
    for (int i = x; i <= n; i += lowbit(i)) t[i] += k;
}
//查询序列前x个数的和
int sum(int x) {
    int sum = 0;
    for (int i = x; i; i -= lowbit(i)) sum += t[i];
    return sum;
}

int main() {
    scanf("%d", &n);
    //读入每个数,分别将数值、序号记录到结构体数组d中
    for (int i = 1; i <= n; i++) {
        scanf("%d", &d[i].val);
        d[i].id = i;
    }
    //对结构体数组进行排序,大的在前,小的在后;如果数值一样,序号大的在前,小的在后
    sort(d + 1, d + 1 + n, cmp);

    //逆序对的定义:i<j && d[i]>d[j]
    // d数组是由大到小排完序的,按由大到小的顺序动态维护树状数组,计算每次变化后出现的i<j 的个数。
    for (int i = 1; i <= n; i++) {
        //将d[i].id放入树状数组,描述这个号的数字增加了1个
        add(d[i].id, 1);
        //查询并累加所有比当前节点id小的数字个数
        ans += sum(d[i].id - 1);
    }
    //输出结果
    cout << ans << endl;
    return 0;
}

三、原版本代码

#include <bits/stdc++.h>
using namespace std;
const int N = 500010;
typedef long long LL;
LL ans;

//原数组/ 离散化后的数组/ 树状数组
int a[N], d[N], t[N];

int n;

bool cmp(int x, int y) {
    if (a[x] == a[y]) return x > y; //避免元素相同
    return a[x] > a[y];             //按照原序列第几大排列
}

//返回非负整数x在二进制表示下最低位1及其后面的0构成的数值
int lowbit(int x) {
    return x & -x;
}
//将序列中第x个数加上k
void add(int x, int k) {
    for (int i = x; i <= n; i += lowbit(i)) t[i] += k;
}
//查询序列前x个数的和
int sum(int x) {
    int sum = 0;
    for (int i = x; i; i -= lowbit(i)) sum += t[i];
    return sum;
}

int main() {
    scanf("%d", &n);
    //离散化,初始化d数组的值为索引号
    for (int i = 1; i <= n; i++) scanf("%d", &a[i]), d[i] = i;
    //对d数组进行排序,排序的依据是a[index]的大小,大的在前,小的在后,如果一样的话,index大的在前
    sort(d + 1, d + n + 1, cmp);
    //如此操作后,d数组记录的是离散化后,第i大的数出现在原来的哪个位置上
    //比如 d[1]=5 ,描述着第1大的数字,原来是第5号的位置

    //遍历d数组,找出正序对,对应着原数组的逆序对
    for (int i = 1; i <= n; i++) {
        add(d[i], 1);
        ans += sum(d[i] - 1);
    }
    cout << ans;
    return 0;
}
posted @ 2022-05-04 17:36  糖豆爸爸  阅读(196)  评论(0)    收藏  举报
Live2D