逆序对(树状数组)题解

 

逆序对题解出处(1266)

老实说,还没有归并排序快

先上代码,再解释

#include<cstdio>
#include<algorithm>
using namespace std;
inline void read(long long &x) {
    x=0;
    long long f=1;
    char s=getchar();
    while(s<'0'||s>'9') {
        if(s=='-')f=-1;
        s=getchar();
    }
    while(s>='0'&&s<='9') {
        x=x*10+s-48;
        s=getchar();
    }
    x*=f;
}
inline void pr(long long x) {
    if(x<0)x=-x;
    if(x>9)pr(x/10);
    putchar(x%10+48);
}
struct node {
    long long num,id;
} a[500005];
long long lsh[500005],c[500005],b[500005],n,k,ans;
inline int lowbit(int x) {
    return x&-x;
}
inline void update(int x,int k) {
    for(int i=x; i<=n; i+=lowbit(i))
        c[i]+=k;
}
inline int sum(int x) {
    int ans=0;
    for(int i=x; i>0; i-=lowbit(i))
        ans+=c[i];
    return ans;
}
inline bool cmp(node a,node b) {
    return a.num<b.num;
}
int main() {
    read(n);
    for(int i=1; i<=n; i++)
        read(a[i].num),a[i].id=i;
    sort(a+1,a+1+n,cmp);
    int cnt=0;
    for(int i=1; i<=n; i++) {
        if(a[i].num!=a[i-1].num)
            cnt++;
        lsh[a[i].id]=cnt;
    }
    for(int i=1; i<=n; i++) {
        update(lsh[i],1);
        ans+=i-sum(lsh[i]);
    }
    pr(ans);
}

现在我们来分段解释(快读快输不解释)

先来看树状数组是个啥

大概就是这么个玩意儿,有什么用后面再解释

然后来看lowbit:

lowbit:

       lowbit(i)的意思是i 转化成二进制数之后,保留最低位的1及其后面的0,截断前面的内容,然后再转十进制数,这个数也是树状数组中i号位的子叶个数。比如lowbit(22)的意思是将 22 转化成二进制数之后得到10110,保留末位的1及其后的0,并截断前面的内容,得到10,转化为十进制数为2,即lowbit(22)=2,证明C[22]的子叶数为2个。

1、求lowbit方法一:

       原数为x(十进制),先将原数转化成二进制之后的最后一位1替换成0,然后再用原数减去替换掉最后一位1后的数(十进制相减),答案就是lowbit(i)的结果;

   lowbit(i)

   {

         return   i - ( i & ( i – 1 ) );

   }

  说明:i的二进制可以看做A1B(A是最后一个1之前的部分,B是最后一个1之后的0)

            i-1的二进制可以看做A0C(C是和B一样长的1)

                       i & (i - 1)的二进制就是A1B & A0C = A0B

            i – (i & (i - 1))的二进制就是A1B – A0B = 0…010…0

2、求lowbit方法二:

       原i(十进制),先将原数转化成二进制之后,在与原数相反数的二进制按位与,答案就是lowbit(i)的结果;

  lowbit(i)

  {

        return   i & -i;

  }

  例如:lowbit(22)=2

            22的二进制原码011010,正数的补码等于它的原码011010

            -22的二进制原码111010,负数的补码等于它的原码取反加1,为100110

            011010  & 100110 = 000010 正数转换成原码后依然是000010

            所以lowbit(22)=2

不懂自己百度

根据这个特点

void update(int k,int x)

  {

        for(int i = k; i <= n; i += lowbit(i))

        C[i] += x;

  }

结合图片,可以求出c数组的值。

然后来看如何求前缀和:

求前缀和B[]段代码    (PS:此区间为前缀和,也就是1~i)   

   int Sum(int k)

   {

    for(int i = k; i > 0; i -= lowbit(i) )

    B[k] += C[i];

    return  B[k]; 

   }

自己结合图像理解

逆序对其实就是求前面有几个比他大的数,然后把每一个的和累加起来就好了

代码分析:

int main() {
    read(n);
    for(int i=1; i<=n; i++)
        read(a[i].num),a[i].id=i;
    sort(a+1,a+1+n,cmp);
    int cnt=0;
    for(int i=1; i<=n; i++) {
        if(a[i].num!=a[i-1].num)//考虑重复的情况,这里可以用STL+二分离散化
            cnt++;
        lsh[a[i].id]=cnt;//离散化可以使数据很大时将数组开小
    }
    for(int i=1; i<=n; i++) {
        update(lsh[i],1);
        ans+=i-sum(lsh[i]);
    }
    pr(ans);
}

在最后为什么要用ans+=i-sum(lsh[i])留给大家自己思考

猜你喜欢

转载自blog.csdn.net/qq_43890190/article/details/84763005