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

题目传送门

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

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

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

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

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

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

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

5放到树状数组t中,此时t只有五个数12345,比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 @   糖豆爸爸  阅读(133)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
历史上的今天:
2020-05-04 Windows下使用SSH密钥实现免密登陆Linux服务器
2013-05-04 我们项目中需要准备的技术
2013-05-04 window 2003 实现多用户远程登录
2013-05-04 XSS的知识普及和预防办法
2013-05-04 免积分下载CSDN软件和新浪资料
Live2D
点击右上角即可分享
微信分享提示