解题思路:

    先熟悉树状数组原理及其应用。
    1.这道题可以转换成求每个位置的左边比他小的个数和右边比他大的个数,这两个相加就是这个人要被交换的次数,然后根据等差数列前n项求和公式(a1+an)*n/2,将所有位置和相加即可。

    2.求逆序数用树状数组优化成 nlogm,树状数组是专门用来求前缀和的,这里从位置0遍历到n-1,把高度作为树状数组的下标,对于输入的小朋友高度a[i],在当前小朋友左边&&比当前小朋友a[i]高的总数,就是树状数组s(maxh)-s(a[i])=i+1-s(a[i]),反之同理。
注意事项:
    1.题中a[i]可以等于0,所以要把输入加一,不然在后面计算sum(a[i]-1)处,会越界。

    2.代码中a数组是保存输入,d数组的下标是高度,lr数组是保存位置i的左右逆序总数,maxh是输入的最大值

参考代码:

#include <iostream>
#include <cstdio>
#define _for(i,a,b) for(int i=a;i<b;i++)
#define _unfor(i,a,b) for(int i=a;i>=b;i--)
#define mset(a,val,n) for(int i=0;i<n;i++)a[i]=val;
#define lowbit(x) (x&(-x))
using namespace std;
typedef long long LL;

int a[100005],d[1000005],n,lr[100005],maxh=0;
//tree_arr
    void add(int i,int x){
        while(i<=maxh+1)d[i]+=x,i+=lowbit(i);
    }

    int sum(int i){ 
        int res=0;
        while(i>0)res+=d[i],i-=lowbit(i);
        return res;
    }
//
int main(){
    scanf("%d",&n);
    _for(i,0,n){scanf("%d",&a[i]);maxh=max(maxh,++a[i]);}
    _for(i,0,n){ //求所有位置的左边的逆序数
        add(a[i],1);
        lr[i]+=i+1-sum(a[i]);
    }
    mset(d,0,maxh+5);
    _unfor(i,n-1,0){//求所有位置的右边的逆序数
        add(a[i],1);z
        lr[i]+=sum(a[i]-1);
    }
    LL ans=0;
    _for(i,0,n){
        LL k=lr[i];
        ans+=k*(k+1)/2;//等差数列求和公式(中间变量需要用LL保存)
    }
    cout<<ans<<endl;
}


 

0.0分

20 人评分

  评论区

是左边有多少个比它大的,右边有多少个比它小的之和吧?
2020-06-05 14:59:58
这个宏定义用的很6
2019-01-14 19:20:58
  • «
  • 1
  • »