头像

欢迎来到我的博客

分享题解与总结

树状数组(3)

逆序对:

题目:

输入序列\(a_1,a_2,a_3,...,a_n\),若\(i<j\)\(a_i>a_j\),则称\(<i,j>\)是一个逆序对,请你计算出序列中逆序对的总数

输入

第一行是整数n,代表序列长度

第二行是n个数\(a_i\)

输出

逆序对总数

思路:

  1. 虚拟地维护一个频次数组freq(i),表示到目前为止i,在序列中出现的次数

    (有些书将此过程称为:把数字看作树状数组的下标)

  2. 倒序遍历离散化后的数组c[i]

    统计:当前数字所能贡献的逆序对数目sum(c[i]-1),更新:树状数组tree[i]


先不考虑离散化,看这样一个例子:

屏幕截图 2026-03-04 112002

屏幕截图 2026-03-04 112207

倒序模版:

#define lowbit(x) ((x)&(-x))
vector<int> tree(N,0),a(N,0),tmp,c;
int n,idx;
ll res;

void update(int x,int d)
{
	while(x<=n){
		tree[x]+=d;
		x+=lowbit(x);
	}
}

int sum(int x)
{
	int ans=0;
	while(x>0){
		ans+=tree[x];
		x-=lowbit(x);
	}
	return ans;
}

int main()
{
	cin>>n;
	for(int i=1;i<=n;++i){//输入
		cin>>a[i];
		tmp.push_back(a[i]);//离散化准备
	}
	sort(tmp.begin(),tmp.end());//排序
	tmp.erase(unique(tmp.begin(),tmp.end()),tmp.end());//去重 利于分配编号
	for(int i=1;i<=n;++i){
		idx=lower_bound(tmp.begin(),tmp.end(),a[i])-tmp.begin()+1;
		c.push_back(idx);//c[i]存储映射后的数字
	}
	for(int i=n-1;i>=0;--i){//用树状数组统计逆序对
		res+=sum(c[i]-1);
		update(c[i],1);
	}
	cout<<res;
	return 0;
}

正序模版:

#define lowbit(x) ((x)&(-x))
int n,max_val=INT_MIN,idx;
ll res;
vector<int> a(M,0),tree(M,0),tmp,c; 
 
void update(int x,int d)
{
	while(x<=n){
		tree[x]+=d;
		x+=lowbit(x);
	}
}

int sum(int x)
{
	int ans=0;
	while(x>0){
		ans+=tree[x];
		x-=lowbit(x);
	}
	return ans;
}
 
int main()
{
	cin>>n;
	for(int i=1;i<=n;++i){
		cin>>a[i];
		tmp.push_back(a[i]);
	}
	sort(tmp.begin(),tmp.end());
	tmp.erase(unique(tmp.begin(),tmp.end()),tmp.end());
	max_val=tmp.size();
	for(int i=1;i<=n;++i){
		idx=lower_bound(tmp.begin(),tmp.end(),a[i])-tmp.begin()+1;
		c.push_back(idx);
	}
	for(int i=0;i<=n-1;++i){
		res+=sum(max_val)-sum(c[i]);//sum(c[i])得到的是序列左边有几个<=c[i]
		update(c[i],1);				//sum(max_val)得到的是我左边有几个数=i个
	}								//作差:序列左边有几个>c[i]
	cout<<res;
	return 0;
}

正序vs倒序:

倒序:从右往左扫,看右边有多少个元素比我“小”

正序:从左往右扫,看左边有多少个元素比我“大”


前缀和问题vs逆序对问题:

前缀和

  1. 原数组:a:{5,4,2,6,3,1}
  2. sum(x):输入完整个数组之后,a[1]+a[2]+...+a[x]的值

逆序对

  1. 原数组:freq:{0,0,0,0,0,0} freq[i]表示遍历到当前位置,数字i出现的次数
  2. sum(x):遍历c:{5,4,2,6,3,1}过程中,freq[1]+freq[2]+...+freq[x]的值

离散化:

  1. freq(i)统计的是 数字i 出现的次数,i最大是\(2^{63} -1\),显然开不了这么大的数组

  2. 但是最多\(5*10^5\)个整数,将这些整数,相对大小不变,映射到\(1\)~\(5*10^5\)即可

  3. 强调:

    离散化去重——tmp数组变小,利于分配编号

    c数组大小不变(c与原始输入数组大小一致)

posted @ 2026-03-04 11:53  king_steph1209  阅读(13)  评论(0)    收藏  举报