题目 1439: [蓝桥杯][历届试题]小朋友排队

题目描述
n 个小朋友站成一排。现在要把他们按身高从低到高的顺序排列,但是每次只能交换位置相邻的两个小朋友。
每个小朋友都有一个不高兴的程度。开始的时候,所有小朋友的不高兴程度都是0。
如果某个小朋友第一次被要求交换,则他的不高兴程度增加1,如果第二次要求他交换,则他的不高兴程度增加2(即不高兴程度为3),依次类推。当要求某个小朋友第k次交换时,他的不高兴程度增加k。
请问,要让所有小朋友按从低到高排队,他们的不高兴程度之和最小是多少。
如果有两个小朋友身高一样,则他们谁站在谁前面是没有关系的。

样例说明:
首先交换身高为3和2的小朋友,再交换身高为3和1的小朋友,再交换身高为2和1的小朋友,每个小朋友的不高兴程度都是3,总和为9。

输入:

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

数据规模和约定
对于100%的数据,1< =n< =1000000< =Hi< =1000000

输出:

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

样例输入:

3
3 2 1

样例输出:

9

思路:
这题我实在是不会,看了题解才发现是我没学的知识盲区—树状数组/线段树
在这里我顺便做个笔记
知道这个知识点的可以翻到后面看源码

1.什么是树状数组?
顾名思义,就是用数组来模拟树形结构呗。那么衍生出一个问题,为什么不直接建树?答案是没必要,因为树状数组能处理的问题就没必要建树。和Trie树的构造方式有类似之处。

2.树状数组可以解决什么问题
可以解决大部分基于区间上的更新以及求和问题。

3.树状数组和线段树的区别在哪里
树状数组可以解决的问题都可以用线段树解决,这两者的区别在哪里呢?树状数组的系数要少很多,就比如字符串模拟大数可以解决大数问题,也可以解决1+1的问题,但没人会在1+1的问题上用大数模拟。

4.树状数组的优点和缺点
修改和查询的复杂度都是O(logN),而且相比线段树系数要少很多,比传统数组要快,而且容易写。

缺点是遇到复杂的区间问题还是不能解决,功能还是有限。

黑色数组代表原来的数组(下面用A[i]代替),红色结构代表我们的树状数组(下面用C[i]代替),发现没有,每个位置只有一个方框,令每个位置存的就是子节点的值的和,则有

C[1] = A[1];
C[2] = A[1] + A[2];
C[3] = A[3];
C[4] = A[1] + A[2] + A[3] + A[4];
C[5] = A[5];
C[6] = A[5] + A[6];
C[7] = A[7];
C[8] = A[1] + A[2] + A[3] + A[4] + A[5] + A[6] + A[7] + A[8];
可以发现,这颗树是有规律的

C[i] = A[i - 2k+1] + A[i - 2k+2] + … + A[i]; //k为i的二进制中从最低位到高位连续零的长度

例如i = 8(1000)时候,k = 3,可自行验证。

这个怎么实现求和呢,比如我们要找前7项和,那么应该是SUM = C[7] + C[6] + C[4];

而根据上面的式子,容易的出SUMi = C[i] + C[i-2k1] + C[(i - 2k1) - 2k2] + …;

其实树状数组就是一个二进制上面的应用。

现在新的问题来了2k该怎么求呢,不难得出2k = i&(i(i-1));但这个还是不好求出呀,前辈的智慧就出来了,2k = i&(-i);

为什么呢?

这里利用的负数的存储特性,负数是以补码存储的,对于整数运算 x&(-x)有
● 当x为0时,即 0 & 0,结果为0;
●当x为奇数时,最后一个比特位为1,取反加1没有进位,故x和-x除最后一位外前面的位正好相反,按位与结果为0。结果为1。
●当x为偶数,且为2的m次方时,x的二进制表示中只有一位是1(从右往左的第m+1位),其右边有m位0,故x取反加1后,从右到左第有m个0,第m+1位及其左边全是1。这样,x& (-x) 得到的就是x。
●当x为偶数,却不为2的m次方的形式时,可以写作x= y * (2k)。其中,y的最低位为1。实际上就是把x用一个奇数左移k位来表示。这时,x的二进制表示最右边有k个0,从右往左第k+1位为1。当对x取反时,最右边的k位0变成1,第k+1位变为0;再加1,最右边的k位就又变成了0,第k+1位因为进位的关系变成了1。左边的位因为没有进位,正好和x原来对应的位上的值相反。二者按位与,得到:第k+1位上为1,左边右边都为0。结果为2k

总结一下:x&(-x),当x为0时结果为0;x为奇数时,结果为1;x为偶数时,结果为x中2的最大次方的因子。
而且这个有一个专门的称呼,叫做lowbit,即取2k

构造一个树状数组:

int n;
int a[1005],c[1005]; //对应原数组和树状数组

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

void updata(int i,int k){    //在i位置加上k
    while(i <= n){
        c[i] += k;
        i += lowbit(i);
    }
}

int getsum(int i){        //求A[1 - i]的和
    int res = 0;
    while(i > 0){
        res += c[i];
        i -= lowbit(i);
    }
    return res;
}

那么回归到这一题:
我们知道最终从低到高排列,而交换的都是不满足前小后大这一规律,所以我们很容易想到逆序对(i < j && A[i] > A[j])。
如果能想到逆序对,那么就已经做对一半了。
小朋友最小交换次数就是该小朋友在(正反逆序对(反:它前面有多少个数大于它; 正:它后面有多少个数小于它))中出现的次数总和,而交换 k 次不高兴程度就增加 k,所以最后每个小朋友的不高兴程度就是 1 ~ k 的累加和。这样只要能统计处该小朋友在所有逆序对
比如样例(3, 2, 1)
反逆序对:0,1,2
正逆序对:2,1,0
总 和:2,2,2
最终对每个小朋友的k进行单独的1~k累加就是答案:9

#include<bits/stdc++.h>
#define maxn 100005 
#define MAXN 1000005
using namespace std;
int c[MAXN], a[MAXN];
long long b[maxn];
int n;

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

int getSum(int x)
{
	int s = 0;
	for(int i = x; i; i -= lowbit(i))
		s += c[i];
	return s;
} 
 

void add(int x, int val)
{
	for(int i = x; i < MAXN; i += lowbit(i)){
		c[i] += val;
	}
}
 
 
int main()
{
	scanf("%d",&n);
	//左边逆序对 
	memset(c,0,sizeof(c));
	for(int i = 0; i < n; i++){
		scanf("%d", &a[i]);
		add(a[i] + 1, 1);
		b[i] = (i + 1) - getSum(a[i] + 1);
	
	}
	//右边逆序对 
	memset(c, 0, sizeof(c));
	for(int i = n-1; i >= 0; i--){
		add(a[i] + 1, 1);
		b[i] += getSum(a[i]);
	}
	long long ans = 0;
	for(int i=0; i<n; i++){
		ans += (1 + b[i]) * b[i] / 2;
	}
	
	printf("%lld\n",ans);
	
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Esaka7

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值