蓝桥杯 历届试题 小朋友排队

  历届试题 小朋友排队  
时间限制:1.0s   内存限制:256.0MB
    
问题描述
  n 个小朋友站成一排。现在要把他们按身高从低到高的顺序排列,但是每次只能交换位置相邻的两个小朋友。

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

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

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

  如果有两个小朋友身高一样,则他们谁站在谁前面是没有关系的。
输入格式
  输入的第一行包含一个整数n,表示小朋友的个数。
  第二行包含 n 个整数 H1 H2 … Hn,分别表示每个小朋友的身高。
输出格式
  输出一行,包含一个整数,表示小朋友的不高兴程度和的最小值。
样例输入
3
3 2 1
样例输出
9
样例说明
  首先交换身高为3和2的小朋友,再交换身高为3和1的小朋友,再交换身高为2和1的小朋友,每个小朋友的不高兴程度都是3,总和为9。
数据规模和约定
  对于10%的数据, 1<=n<=10;
  对于30%的数据, 1<=n<=1000;
  对于50%的数据, 1<=n<=10000;
  对于100%的数据,1<=n<=100000,0<=Hi<=1000000。

在这一题里我也卡了一段时间

一开始我单纯的一位是排序题采用冒泡排序解决

然后写了一套排序解决方案

然后样例数据通过后进行提交发现只通过了前三个数据


并且6-10题都是运行超时

后来查询网上一些其他大神的资料发现冒泡排序在这一题里并不能有不开心数的最小值

而且因为排序需要遍历两次在题目要求中的1000000这么大的数据里循环两次必然会运行超时

后来查询资料得知这一题不开心值的解决方案最小值求法是找逆序对

使用样例数据中的例子来打比方

输入数据是 3 2 1

要从小到大排列则3至少要交换两次【因为3后面有两个数比它小,即3与2 必然交换一次,3与1也必然交换一次】

要排列2也至少交换两次【因为2前面有一个数3比它大,后面有一个数1比它小】

排列1也是至少交换两次【因为1前面有两个数比它大】

以此类推,可以得知每一个数要交换的次数为这个数前面比它大的数+后面比它小的数,即逆序对数

知道这一个规律后使用for循环也可以求出这些值,但是至少也需要两轮for循环来遍历每一个数

在题目的最大数据规模里,必然会运行超时

因此不能使用for循环遍历每个数来求最小交换次数

后来看别人的解法还有查询资料得知有一种快速求前面所有数的和的方法叫做树状数组

树状数组具体理解可以看这一篇大神的博文https://www.cnblogs.com/ECJTUACM-873284962/p/6380245.html

我这里不详细讲解树状数组的实现等,只讲解解法和功效

树状数组常用于区间查询任意两位元素之间所有元素之和

在此题中可以采用这种方法快速求逆序对

首先用这种方法存储所录入的身高数据

设正在录入的身高为x,然后将c[x+1]=c[x+1]+1(数组c的初始值为0,这一种存储方法可以想象为这一群不同身高的学生,老师叫他们按照身高站,身高为x的学生统一站到x+1群里,则有几个身高为x的学生,c[x]就等于几.)【为什么要x+1?因为树状数组操作空间是1~正无穷,而题目中可能的身高值里有0】

用这种方法存储身高,只要得知c[1]~c[x+1]的和,就能知道前面有多少人比自己矮【虽然这个是顺序的,我们需要获得的是逆序对,但是获取到这个数据也是有用的】

要知道前面有多少人比自己高则可以这样子求,已经录入的数据值-当前身高的值的人数-比当前身高矮的人数【比如总共需要录入n个数据,现在在录第i个数据,身高为x,则n-i-c[x+1]就是比自己高的人数】,即已经录入的人数减去比自己高的和自己一样高的人的人数,剩余的人数就是比自己矮的。

然后需要的数据为后面录入的人里比自己矮的人数,这个可以采用一样的方法,先重置c数组,然后逆序进行重新录入。【此处重新录入是需要把第一次录入的值存储下来,即一开始输入数据为3 2 1 ,逆序录入时录入 1 2 3 】,然后在此时需要的是获取到比自己矮的人数,当前录入身高为x,想知道多少比自己矮可以直接获取c[1]~c[x+1]的和。

具体代码如下

#include<iostream>
#include<cstring>
using namespace std;
//a为原数组,b为按大小排序的数组,c为b的树状数组,d为暂存的不开心值 ,e为等差数列存储不高兴值的递增值
int a[100001],b[1000003],c[1000003],d[1000003];
long long int e[100002];
//位运算
int lowbit(int x)
{
    return x&(-x);  
}
//定义修改C数组的函数,第一个值代表数组位置,第二个代表数组值,在此题中只需默认加一
void update(int local,int num){
	int n=local,add;
	add=num-b[local];
	b[local]=num;
	while(n<1000002){
		c[n]+=add;
		n+=lowbit(n);
	}
}
//查询前多少区间的和
int read(int i){
	int n=i,sum=0;
	while(n!=0){
		sum+=c[n];
		n-=lowbit(n);
	}
	return sum;
}
//清零函数
void ini(){
	memset(b,0,sizeof(b));
	memset(c,0,sizeof(c));
}
int main(){
	int i,n;
	long long int sum=0;
	cin>>n;
	ini();
	for(i=0;i<n;i++){
		cin>>a[i];//按顺序存储进来的小朋友身高
		update(a[i]+1,b[a[i]+1]+1);//修改b和c数组,
		d[i]=i-read(a[i])-b[a[i]+1]+1;//计算在第i个小朋友进来时,前面比他高的人数
	}
	ini();
	for(i=n-1;i>=0;i--){
		update(a[i]+1,b[a[i]+1]+1);//修改b和c数组,
		d[i]+=read(a[i]);//计算在第i个小朋友进来时,后面比他矮的人数
	}
	e[1]=1;
	for(i=2;i<n+1;i++){
		e[i]=e[i-1]+i;
	}
	for(i=0;i<n;i++){
		sum+=e[d[i]];
	}
	cout<<sum;
	return 0;
}

里面需要注意的是一些地方因为数据过大超过了int 存储范围

第一处是总不开心值sum,这里不采用long long int的话可能会超出数据范围导致无法通过样例数据【这里的报错包括下面的那个没有用long long int 的错误】


另一处需要用long long int的地方是

long long int e[100002];

这里引起的报错是


这个数组是我用来存储不开心值的等差数列,即当小朋友交换一次时不开心值为1则e[1]=1,交换两次不开心值为1+2即e[2]=e[1]+2=3,虽然可以使用等差数列求和公式来快速获取最后值,但是考虑到交换次数可能重复,所以采用数组存储具体值,避免计算使用消耗内存。【虽然好像因为里面有很多多余的计算也消耗了很多内存。。。】

总结经验是对于大规模数据运算,对于不确定数据规模会不会过大时要记得用long long int,不然找bug找半天

其实经验是学习快速区间求值的方法是树状数组 

猜你喜欢

转载自blog.csdn.net/ever_evil/article/details/79323372