树状数组求逆序对
-
之前求逆序对一直用的都是归并排序的方法,后来发现大家都是用树状数组来求解的,因为树状数组不仅写起来简单,还可以动态维护一些东西.
-
对于一个序列,我们正向遍历他的所有元素,然后每次把当前元素插入到树状数组中,即\(c[a[i]]=1\),然后看区间\(c[a[i]],max(a[i])]\)中有多少个已经被插入的元素
举个例子:5 6 1 2 3 4
刚开始的\(c\)数组为:\(0,0,0,0,0,0\),插入\(5\)后为:\(0,0,0,0,1,0\),插入\(6\)后为:\(0,0,0,0,1,1\),插入\(1\)后为\(1,0,0,0,1,1\),此时发现在\(1\)的后面\(5\)和\(6\)已经被插入了,即\(c[2,6]\)的后缀和为\(2\),所以此时产生了2个逆序对,对\(2,3,4\)也是同理.
-
那么我们要怎么去统计这个后缀区间的和呢?其实很简单,我们遍历到第\(i\)的数的时候是不是插入了\(i\)个数?根据树状数组的性质,我们可以求出\([1,a[i]]\)的前缀和,由于一共插入了\(i\)个数,而\([1,a[i]]\)的个数我们可以求出,那么后缀和不就是为:\(i-sum[a[i]]\)吗?
-
\(a[i]\)比较小的时候可以直接用\(c\)记录\(a[i]\),但是\(a[i]\)很大的话,是不能用作下标被\(c\)数组存的,所以此时我们就需要用离散化来进行操作.具体实现方式是:先用一个新数组\(all\)copy一下\(a\).然后对\(all\)排序再用unique函数去重,这样我们就可以将\(a\)数组映射成一些很小的数字,然后遍历的时候可以用二分查找\(a[i]\)在\(all\)数组所对应的值,
-
代码:
int n; ll a[N]; ll c[N]; vector<ll> all; ll lowbit(ll x){ return x&(-x); } void updata(ll i,int k){ while(i<=n){ c[i]+=k; i+=lowbit(i); } } ll get_sum(ll i){ ll res=0; while(i){ res+=c[i]; i-=lowbit(i); } return res; } int main() { ios::sync_with_stdio(false);cin.tie(0);cout.tie(0); cin>>n; for(int i=1;i<=n;++i){ cin>>a[i]; all.pb(a[i]); } sort(all.begin(),all.end()); all.erase(unique(all.begin(),all.end()),all.end()); ll ans=0; for(int i=1;i<=n;++i){ int cur=lower_bound(all.begin(),all.end(),a[i])-all.begin(); updata(cur+1,1); ans+=i-get_sum(cur+1); } cout<<ans<<endl; return 0; }
𝓐𝓬𝓱𝓲𝓮𝓿𝓮𝓶𝓮𝓷𝓽 𝓹𝓻𝓸𝓿𝓲𝓭𝓮𝓼 𝓽𝓱𝓮 𝓸𝓷𝓵𝔂 𝓻𝓮𝓪𝓵
𝓹𝓵𝓮𝓪𝓼𝓾𝓻𝓮 𝓲𝓷 𝓵𝓲𝓯𝓮