【做题笔记】洛谷P1908逆序对

树状数组的奇技淫巧(雾)

听说此题可以用线段树乱搞?

反正我不会/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] 内的数的个数。

举个栗子:

3hO2GV.png

那么回到本题,由于是倒序扫描,那么扫描到 (i) 时扫描的也就是“ (a_i) 在序列之后的那些数出现的个数”,能够保证 (i<j)。放个图:

3hvkDg.png

同时,由于查询的是 (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;
}
原文地址:https://www.cnblogs.com/BlueInRed/p/12404511.html