洛谷 P4378 —— 树状数组求逆序对

题意:
求冒泡排序算法的“趟数”
思路:
一趟冒泡排序可以可以还原一个数的一个逆序对,那么我们需要求出一个数最多的逆序对个数就是冒泡的趟数。
拿样例来举个例子:

5
1
5
3
8
2
有逆序对(5,3(5,2)(3,2)(8,2)  其中逆序对最多的是 2
第一趟冒泡以后 变成 1 3 5 2 8 消除了(53)(82)
第二趟冒泡以后 变成 1 3 2 5 8 消除了(52)
第三趟冒泡以后 变成 1 2 3 5 8 消除了(32)

除了朴素求逆序对,常见的求逆序对主要有两种方法。

  1. 归并排序求逆序对
  2. 树状数组求逆序对

此题用归并排序的方法无法确认最多的逆序对个数(也可能存在,但是我不会…)
所以采用树状数组求逆序对,顺便记录一下树状数组求逆序对的原理:
众所周知,树状数组主要是用来维护前缀和,所以我们要把求逆序对的问题转换成一个前缀和问题。
步骤:

  1. 先将数列排序
  2. 然后按原序列的顺序,把数列中的数插入到树状数组中。在插入数的时候,将已经插入的数所在的位置 ii ,打一个标记值 设bk[i]=1bk[i] = 1每次拿已经插入的数字个数 tottot - bk[]bk[] 的前 ii项和 把其中的差累加就是 逆序对的个数。
    拿样例来举个例子:
    在这里插入图片描述
  • 第一次插入 11 ,在下标 11 下面打个标记 1,共插入tot=1tot = 1个数,前 11 个数的前缀和为sum=1totsum=0ans=0sum = 1,tot - sum = 0,ans = 0

  • 第二次插入 55 ,在下标 44 下面打个标记 1,共插入tot=2tot = 2个数,前 44 个数的前缀和为sum=2totsum=0ans=0sum = 2,tot - sum = 0,ans = 0

  • 第三次插入 33 ,在下标 33 下面打个标记 1,共插入tot=3tot = 3个数,前 33 个数的前缀和为sum=2totsum=1ans+=1ans=1sum = 2,tot - sum = 1,ans += 1,ans = 1

  • 第四次插入 88 ,在下标 55 下面打个标记 1,共插入tot=4tot = 4个数,前 55 个数的前缀和为sum=4totsum=0ans=0sum = 4,tot - sum = 0,ans = 0

  • 第五次插入 22 ,在下标 22 下面打个标记 1,共插入tot=5tot = 5个数,前 22 个数的前缀和为sum=2totsum=3ans+=3,ans=4sum = 2,tot - sum = 3,ans += 3,ans = 4

共4个逆序对。
原理:
注:a[]a[]是排好序的数列。

因为插入的顺序是原数组的顺序,但是标记按排好序的顺序打的,这完全符合逆序对的定义:假设在原数组中 a[i]a[i] 排在 a[j]a[j] 前面,我们先插入a[i],ia[i],i 处打上标记,然后再插入a[j],ja[j],j 处打上标记, 此时如果i<ji<jiijj的前面 ,我们统计前 jj 个数的前缀和sumsum是可以统计到 ii 的,相反如果i>ji>j,ii 就在 jj 的后面,我们就统计不到,所以在 jj 前面插入 ,又大于a[j]a[j]的数就可以和a[j]a[j] 构成逆序对。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define LL long long
using namespace std;
 
const int N = 1e5+7;
int n,tree[N];
int ans;
void add(int k,int num)
{
    while(k<=n)
    {
        tree[k]+=num;
        k+=k&-k;
    }
}
 
int read(int k)
{
    int sum=0;
    while(k)
    {
        sum+=tree[k];
        k-=k&-k;
    }
    return sum;
}
struct node
{
    int val,pos;
}a[N];
bool cmp(node a,node b)
{
    return a.val < b.val;
}
int main(void)
{
	//freopen("data.in","r",stdin);
    int i,j;
    int b[N];
    while(scanf("%d",&n)==1)
    {
        memset(tree,0,sizeof(tree));
        for(i=1;i<=n;i++)
        {
            scanf("%d",&a[i].val);
            a[i].pos = i;
        }
        sort(a+1,a+1+n,cmp);
        int cnt = 1;
        for(i=1;i<=n;i++)
        {
            if(i != 1 && a[i].val != a[i-1].val)
                cnt++;
            b[a[i].pos] = cnt;
        }
        for(i=1;i<=n;i++)
        {
            add(b[i],1);
            ans = max(ans,i - read(b[i]));
        }
        printf("%lld\n",ans+1);
    }
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章