【洛谷】P8613 [蓝桥杯 2014 省 B] 小朋友排队 题解
题目
整体思路:使用树状数组优化,求逆序对
先熟悉树状数组原理及其应用。
-
这道题可以转换成求每个位置的左边比他小的个数和右边比他大的个数,这两个相加就是这个人要被交换的次数,然后根据等差数列前 \(n\) 项求和公式 $ ( a_1 + a_n ) \times \dfrac{n}{2}$,将所有位置和相加即可。
-
求逆序数用树状数组优化成 \(O(n \log m)\),树状数组是专门用来求前缀和的,这里从位置 \(0\) 遍历到位置 \(n-1\),把高度作为树状数组的下标,每个高度对应的个数作为树状数组的值,对于输入的小朋友高度 \(a_i\),在当前小朋友左边并且比当前小朋友 \(a_i\) 高的总数,就是树状数组。\(\operatorname{s}(maxh)-\operatorname {s}(a_i)=i+1-\operatorname {s}(a_i)\),反之同理。
注意事项:
-
题中 \(a[i]\) 可以等于 \(0\),所以要把输入加一,不然在后面计算 \(\operatorname {sum}(a_i-1)\) 处,会越界。
-
代码中 \(a\) 数组是保存输入,\(d\) 数组的下标是高度,\(lr\) 数组是保存位置 \(i\) 的左右逆序总数,$ maxh $ 是输入的最大值
代码
#include <bits/stdc++.h>
#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);
lr[i]+=sum(a[i]-1);
}
LL ans=0;
_for(i,0,n){
LL k=lr[i];
ans+=k*(k+1)/2;
}
cout<<ans<<endl;
}

浙公网安备 33010602011771号