树状数组应用及例题(一)

  介绍完了树状数组(有兴趣的读者在我的博客文章中自行阅读),我们知道它是一种高效处理动态区间和查询与修改问题的数据结构,那么树状数组还有哪些其他的应用呢?一起来看一下这道例题:

猫猫TOM和小老鼠JERRY最近又较量上了,但是毕竟都是成年人,他们已经不喜欢再玩那种你追我赶的游戏,现在他们喜欢玩统计。最近,TOM老猫查阅到一个人类称之为“逆序对”的东西,他们就比赛谁先算出给定的一段正整数序列中逆序对的数目。

输入格式:

第一行,一个数n,表示序列中有n个数,第二行n个数,表示给定的序列。

输出格式:

给定序列中逆序对的数目。(对于100%的数据,n≤40000。)

   题意非常明白,就是给出一行数,求出他其中逆序对的个数,所谓逆序对,在A1,A2,A3...Ai...Aj...中满足Ai>Aj&&i<j,不难写出以下的暴力枚举算法:

#include <bits/stdc++.h>
using namespace std;
int main(){
    int n,num[40005],ans=0;
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&num[i]);
    for(int i=n;i>=1;i--){
        for(int j=n;j>i;j--){
            if(num[j]<num[i]) ans++;
        }
    }
    printf("%d",ans);
    return 0;
}

  那么它的时间复杂度是多少呢?T(n)=O(n^2),对于题目中n<=40000会超时,那么我们就来分析一下题。

  由于是求逆序对个数,判断的参数只涉及大小,不涉及数值,也不要求将逆序对依次输出,为简化编程复杂度和优化空间复杂度,可以想到离散化(discretization)。但这不是问题的关键,如何优化时间复杂度呢?我们可以接着暴力的思想来继续思考,每次从序列中取出一个数,然后计算它在已经取出的数中能构成多少对逆序对,然后把已有答案加上它,更新答案......停!这不是就能用树状数组来优化么,和经典例题相比不同点在于更新数字时只需把父亲节点加1即可,做法便是这样,具体细节留给读者自行思考,代码如下:

#include <bits/stdc++.h>
using namespace std;
const int MAXN=40005;
int C[MAXN],n,a[MAXN],dis[maxn];//discretization离散化
struct Node{
	int value;
	int order;
}a[MAXN];
bool cmpn(Node x,Node y){
	return x.value<y.value;
}
int lowbit(int v){return v&(-v);}
void Add(int x,int value){
	while(x<=N){
		C[x]+=value;
		x+=lowbit(x);
	}
	return;
}
int pre_Query(int k){
	int sum=0;
	while(k>0){
		sum+=C[k];k-=lowbit(k);
	}
	return sum;
}
int sec_sum(int L,int R){
	return pre_Query(R)-pre_Query(L-1);
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i].value);
		a[i].order=i;
	}
	sort(a+1,a+n+1,cmpn);
	for(int i=1;i<=n;i++) dis[a[i].order]=i;
	int ans=0;
	for(int i=1;i<=n;i++){
		Add(dis[i],1);
		ans+=i-pre_Query(dis[i]);
	}
	printf("%d",ans);
	return 0;
}

  关于树状数组这一内容,我还会陆续更新更多的例题及分析。

  有兴趣的读者可以关注我,之后会更新更多的有关数据结构或是算法的讲解,也可以加我的QQ1552611369,,讨论算法。

猜你喜欢

转载自blog.csdn.net/qq_36524645/article/details/81708415