小朋友排队(树状数组)

n 个小朋友站成一排。

现在要把他们按身高从低到高的顺序排列,但是每次只能交换位置相邻的两个小朋友。

每个小朋友都有一个不高兴的程度。

开始的时候,所有小朋友的不高兴程度都是 0。

如果某个小朋友第一次被要求交换,则他的不高兴程度增加 1,如果第二次要求他交换,则他的不高兴程度增加 2(即不高兴程度为 3),依次类推。当要求某个小朋友第 k 次交换时,他的不高兴程度增加 k。

请问,要让所有小朋友按从低到高排队,他们的不高兴程度之和最小是多少。

如果有两个小朋友身高一样,则他们谁站在谁前面是没有关系的。

输入格式
输入的第一行包含一个整数 n,表示小朋友的个数。

第二行包含 n 个整数 H1,H2,…,Hn,分别表示每个小朋友的身高。

输出格式
输出一行,包含一个整数,表示小朋友的不高兴程度和的最小值。

数据范围
1≤n≤100000,
0≤Hi≤1000000
输入样例:
3
3 2 1
输出样例:
9
样例解释
首先交换身高为3和2的小朋友,再交换身高为3和1的小朋友,再交换身高为2和1的小朋友,每个小朋友的不高兴程度都是3,总和为9。


每个数所对应的换位次数(逆序对)=前面比它大的+后面比它小的
每次读入一个数就先把它放到树状数组中去,但这个树状数组保存的并不是这个数,而是这个数出现的次数
只要明白了这个道理就很简单了,放入完毕后,我们就去query一下这个数之前有多少个比他小的数,然后用当前数组长度减去比这个数小的数就是比这个数大的数的个数了(算比这个数大的个数)。
比当前数靠后的且别当数小的数求法就是把这个树状数组给倒过来求一遍。
5 2 3 4 1
当我们判断到第四个数也就是4的时候,我们树状数组维护了那些信息,仔细看我上面加粗的字体,在4之前5出现了1次,2出现了一次,3出现了1次此时,树状数组(把0空过去,数据都不考虑0,即使出现0我们让每个数都++,这样不影响结果也不出现0)tr[1]=0,tr[2]=1,tr[3]=1,tr[4]=1,tr[5]=1,而在4之前比4大的数只有5,怎么算,在这里我们并不知道比4大的数就是5,也可能是另一个数,但我们只需要知道比4大的个数就行了,那我们用当前的个数减去比4小的个数不就好了,对啊,所以说比他大的数4-query(4)=1,比4小的个数求法,我们倒着看,怎么看着简单呢?,你把数组倒过来,看成1,4,3,2,5,比他小的就是query(4)=2(注意,在这里,两个数组是一样的,所以其维护的树状数组里的值也是不同,所以query(4)不同!!!,query就是计算比当前数(例子中是4)小的个数之和),所以4这个小盆友他的换位次数2+1=3,可4逆序数对是2怎和结论冲突了呢,实际上query这个操作把自己也算进去了,而4只能被算一次,所以求比4小的数应该算成query(3)而不是query(4),因为4已经在上面算过了,因为树状数组中的区间和[x-lowbit(x),x]是闭区间,所以说把x算进去了。

#include<iostream>
#include<algorithm>
#include<cstring>

using namespace std;

typedef long long ll;

const int N = 1000010;

int n;
ll h[N], sum[N];
ll tr[N];

int lowbit(int x)
{
	return x & -x;
}

void add(int x, int y)
{
	for(int i = x; i <= N; i += lowbit(i))
		tr[i] += y;
}

int query(int x)
{
	int res = 0;
	for(int i = x; i; i -= lowbit(i))
		res += tr[i];
	return res;
}

int main()
{
	cin >> n;
	for(int i = 0; i < n; i ++) 
	{
		cin >> h[i];
		h[i] ++;//避免0 
	}
	
	//前面有多少比它大
	for(int i = 0; i < n; i ++)
	{
		sum[i] = i - query(h[i]);
		add(h[i], 1);
	} 
	
	memset(tr, 0, sizeof(tr)); //初始化数组 
	//后面有多少比它小
	for(int i = n - 1; i >= 0; i --)
	{
		sum[i] += query(h[i] - 1);
		add(h[i], 1);
	} 
	
	ll res = 0;
	for(int i = 0; i <= n; i ++)
		res += sum[i] * (sum[i] + 1) / 2;
		
	cout << res << endl;
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值