树状数组之求逆序对

树状数组之求逆序对

逆序对

   设 A 为一个有 n 个数字的有序集 (n>1), 其中所有数字各不相同。 如果存在正整数 i, j 使得 1 ≤ i < j ≤ n 而且 A [i] > A [j], 则 <A [i], A [j]> 这个有序对称为 A 的一个逆 序对,也称作逆序数。 例如,数组 (3,1,4,5,2) 的逆序对有 (3,1),(3,2),(4,2),(5,2), 共 4 个。

原理

我们有以下的设定

  • a[]={0,3,1,4,5,2} 其中 3,1,4,5,2 是我们求逆序对序列
  • c[6]={0} 是一个树状数组,初始为空
  • A[i] 表示大小为 i 的数出现了几次

如图:
在这里插入图片描述

核心: 在我操作第 i 个数的时候,已经有 k 个数比第 i 个数大,那这个时刻我们知道有 k 个逆序对.怎么求 k?
     k=i−query(a[i])

如果我们进行下面的操作:

for(i=1;i<=5;i++){ 
    update(a[i],1); 
}
  • i=1 的时候,c[3]=1,c[4]=1, 那么 query(3)=1,query(4)=1
  • query(3)=1 表示 1->3 范围内的数字有一个
  • query(3)=1 也可以表示,前 1 个数中,<=3 的数有一个
  • 1-query(3)=0 表示:前 1 个数中,>3 的数有 0 个
  • 同理 i=2, 时候 update(a[2],1)
  • query(a[2]) 表示前 2 个数中,<=a[2] 的数有 query(a[2]) 个
  • 2-query(a[2]) 表示前 2 个数中,>a[2] 的数有 2-query(a[2]) 个

核心思想:在处理到第 i 个数的时候,i−query(a[i]) 表示有前 i 个数之中有几个数比第 i 个数大,将所有结果加起来就是逆序对的数量

如图:
在这里插入图片描述

核心操作

部分操作见 树状数组之前缀和

#include <iostream>
#include <string.h>'
using namespace std;

#define mem(a,b) memset(a,b,sizeof(a))
#define Ios ios::sync_with_stdio(false) , cin.tie(0) , cout.tie(0);
#define ll long long

const int N = 500056;
int c[N],ans[N];
int n;

int lowbit(int x) { return (x & -x); }
int getsum(int n){
    int sum = 0;
    while(n>0){
        sum += c[n};
        n -= lowbit(n);
    }
    return sum;
}
void change(int x,int num){
    while(x>=n}{
        c[x] += num;
        x += lowbit(x);
    }
}

int main(){
    scanf("%d",&n);
    ll cnt = 0;
    for(int i=1; i<=n; i++){
        int t;
        scanf("%d",&t);
        chang(t,1): // 求逆序对就要初始化c数组为1
        cnt += (ll)i - getsum(t); //核心思想
    }
    printf("%d",cnt);
    return 0;
}

例题

luogu P1966 [NOIP2013 提高组] 火柴排队

在这里插入图片描述

思路:

  • 定义一个结构体,来存储每排火柴的高度和每排所呆的位置

  • 由于高度差是相对的,所以可以用第一排或者第二排做参照找出其有多少个大小不一的即逆序对即可

  • 其中相对的参照算法:

    • 先对两排的火柴按高度进行排序(结构体排序)
    • 开一个数组S,记录排完序后两排火柴之间的相对位置,这里我们S的下标为第一排排完序的下标,其值为对应的第二排排完序的下标即:
      for (int i = 1; i <= n; i++) s[a[i].pos] = b[i].pos;
#include <iostream>
#include <string.h>
#include <algorithm>
#include <math.h>using namespace std;

#define mem(a,b) memset(a,b,sizeof(a))
#define ll long long

const int N = 600005;
const int mod = 99999997;

int n;
long long c[N],s[N];      //c[n]表示a[1~n]的和,a数组省略,s[N]是参照对比后的数组
struct node {
    int val, pos;
}a[N], b[N];

int lowbit(int x) {
    return (x & -x);
}
ll getsum(int n) {
    ll sum = 0;
    while (n > 0) {
        sum = (sum + c[n]) % 99999997; //以防传入时的数据过大,这里提前mod
        n -= lowbit(n);
    }
    return sum;
}
void change(int x) {
    while (x <= n) {
        c[x]++;
        x += lowbit(x);
    }
}
bool cmp(node a, node b) {         //结构体排序,注意包含相同数时的情况,否则会报段错误
    if (a.val != b.val)
        return a.val > b.val;
    else return a.pos > b.pos;
}

int main() {
    cin >> n;
    mem(c, 0);
    mem(s, 0);
    node a[N];
    node b[N];
    //数据传入
    for (int i = 1; i <= n; i++) {
        cin >> a[i].val;
        a[i].pos = i;
    }
    for (int i = 1; i <= n; i++) {
        cin >> b[i].val;
        b[i].pos = i;
    }
    //排序
    sort(a + 1, a + n + 1, cmp);
    sort(b + 1, b + n + 1, cmp);
    //进行参照对比,得到以身高为参照的位置信息
    for (int i = 1; i <= n; i++)
        s[a[i].pos] = b[i].pos;
    ll cnt = 0;
    for (int i = 1; i <= n; i++) {
        change(s[i]); 
        cnt += i-getsum(s[i]);        
    }
    cout << cnt % 99999997 << endl;
    return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值