树状数组的奇技淫巧(雾)
听说此题可以用线段树乱搞?
反正我不会/xyx
然后此题树状数组思路极其清晰,老少皆宜
题目大意
(a_i) 和 (a_j) 成逆序对当且仅当 (i<j) 且 (a_i>a_j) 。
现给出一个长度为 (n) 的序列 (a) ,问此序列珂构成多少个逆序对。
solution
在 (a) 的值域上建立树状数组。
首先倒序扫描 (a) ,然后使答案 (ans) 加上 ( ext{ask}(a_i-1))。然后执行 ( ext{add}(a_i,1))。
其中 ask(x) 表示查询 x 的前缀和,add(x,v) 表示把 x 的位置+v。
这样做为啥是对的呢?首先了解这样一个东西:
- 设 (t_{val}) 表示 val 在序列 a 中出现的次数,那么把 [l,r] 内 t 的值累加,得到的就是 a 序列中值域在 [l,r] 内的数的个数。
举个栗子:
那么回到本题,由于是倒序扫描,那么扫描到 (i) 时扫描的也就是“ (a_i) 在序列之后的那些数出现的个数”,能够保证 (i<j)。放个图:
同时,由于查询的是 (a_{i}-1) 的前缀和,也就保证了查询发现的数一定保证 (a_i>a_j)。
累计完答案后,再把树状数组中代表 (a_i) 的值加一,代表 (a_i) 又出现了一次,类似于上边说的更新 t 数组。
整个一套过程下来,完美解决了这个操作。
注意到 (a_i) 的数值很大,所以需要离散化。
然后这题离散化时不需要去重。不需要考虑重复元素可能带来的影响:因为 lower_bound 是查找大于等于 x 的第一个位置,那么相同的数就会得到一个相同的排名。
参考代码
#include <iostream>
#include <stdio.h>
#include <algorithm>
#define lowbit(x) x & -x
using namespace std;
int n;
long long ans,a[5*500005],c[5*500005],b[5*500005];
inline void add(int x,int v)
{
for(int i=x;i<=n;i+=lowbit(i)) c[i]+=v;
}
inline int ask(int x)
{
long long ans=0;
for(int i=x;i;i-=lowbit(i)) ans+=c[i];
return ans;
}
inline int read()
{
int s=0,w=1; char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-') w=-1;ch=getchar();}
while(ch>='0'&&ch<='9') s=s*10+ch-'0' , ch=getchar();
return s*w;
}
int main()
{
n=read();
for(int i=1;i<=n;i++) a[i]=b[i]=read();
sort(b+1,b+n+1);
for(int i=1;i<=n;i++) a[i]=lower_bound(b+1,b+n+1,a[i])-b;
for(int i=n;i;i--)
{
ans+=ask(a[i]-1);
add(a[i],1);
}
cout<<ans<<endl;
return 0;
}