蓝桥杯训练题题目 1439: [蓝桥杯][历届试题]小朋友排队【考察查找逆序对的数目,方法一:直接利用树状数组得出,方法二:离散化后,再用树状数组得出】

题目链接:https://www.dotcpp.com/oj/problem1439.html

题目描述:

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

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

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

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

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

样例说明
首先交换身高为32的小朋友,再交换身高为31的小朋友,再交换身高为21的小朋友,每个小朋友的不高兴程度都是3,总和为9。
数据规模和约定
对于100%的数据,1< =n< =1000000< =Hi< =1000000

样例:


**输入**

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

**输出**
输出一行,包含一个整数,表示小朋友的不高兴程度和的最小值。
**样例输入**
3
3 2 1
**样例输出**
9

思路:

首先就是观察小朋友的不开心指数是根据交换次数来的,并且两者关系是等差数列之和。那么也就是说,我们找出每个小朋友的交换次数就可以解决问题了。

通过观察数据我们可以发现,每个小朋友的交换次数是其左边比他大的数字个数加上右边比他小的数字个数之和。
其实,这找左边的比他大的数字个数,就是找经典的“逆序对”,并称其为“正逆序对”,而找右边比他小的数字同样是找“逆序对”的思想,但因为是找比他小的,所以称为“反逆序对”。
比如说题目给的数据:3 2 1这三个数字,
拿3来看:左边比他大的数字为0个,右边比他大的数字为2个,所以交换次数为2次。
拿2来看:左边比他大的数字为1个,右边比他大的数字为1个,所以交换次数为2次。
拿1来看:左边比他大的数字为2个,右边比他大的数字为0个,所以交换次数为2次。

转化成式子表达就是:

3 2 1
正逆序对:2 1 0
反逆序对:0 1 2
和为: 2 2 2
所以最后的和为:(1+2)*2/2=3、(1+2)*2/2=3、(1+2)*2/2=3
为3+3+3=9
故输出9

方法一:直接利用树状数组得出【如果不知道树状数组的小白请点这里:传送门

这里说两个细节性的东西,坑了我好久,我哭了:
①第一个:最后记录每次的结果,以及最终的结果都要用long long来存储

	ll res[mx];//每次的结果要用ll来存储,不然就会导致答案不全 
	-----中间的代码-------
	ll ans = 0;

②第二个:最开始接受a的值进来每个都要+1,因为要保证进入树状数组的下标是从>=1开始的。

-----代码------
a[i]++;//让树状数组里的下标是>=1的,没有这句话会超时 
-------代码--------

然后说下:取正逆序对和反逆序对怎么个取法

正逆序对

	for(int i = 1;i <= n;i++){//从前往后取出正逆序对
		cin>>a[i];
		a[i]++;//让树状数组里的下标是>=1的,没有这句话会超时 
		update(a[i],1);
		res[i] +=i-getsum(a[i]);
	}

反逆序对

	for(int i = n;i >= 1;i--){//从后往前取出取出反逆序对 
		update(a[i]+1,1);
		res[i] += getsum(a[i]);
	}

方法一的ac代码:

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

const int mx = 100005;
const int maxn = 1000010;
int c[maxn];
int a[mx];


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

void update(int i,int k){

	while(i < maxn){
		c[i] += k;
		i += lowbit(i);
	}
}

int getsum(int i){
	int ans = 0;
	while(i > 0){
		ans += c[i];
		i -= lowbit(i);
	}
	return ans;
}

int main(){
	int n;
	ll res[mx];//每次的结果要用ll来存储,不然就会导致答案不全 
	
	
	cin>>n;
	
	memset(c,0,sizeof(c));
	memset(res,0,sizeof(res));
	
	for(int i = 1;i <= n;i++){//从前往后取出正逆序对
		cin>>a[i];
		a[i]++;//让树状数组里的下标是>=1的,没有这句话会超时 
		update(a[i],1);
		res[i] +=i-getsum(a[i]);
	}
	
	
	memset(c,0,sizeof(c));
	
	for(int i = n;i >= 1;i--){//从后往前取出取出反逆序对 
		update(a[i]+1,1);
		res[i] += getsum(a[i]);
	}
	
	ll ans = 0;
	for(int i = 1;i <= n;i++){
//		cout<<res[i]<<" ";

		ans += ((1+res[i])*res[i])/2;
		
	}
//	cout<<endl;
	cout<<ans<<endl;
	return 0;
}

方法二:离散化后,再用树状数组得出

这里就直接说下离散化就是了,离散化的目的就是防止接收的初始数据太大导致的树状数组的空间也需要很大,从而导致空间越界。所以我们就利用离散化,将初始数组的值的相对大小关系转化成其下标的相对大小关系。

离散化:
①:我们需要一个与初始数组一样的数组,里面的值也要一样,然后需要一个map,去记录值和坐标值的关系。下面的temp1和temp2其实是一样的,mp和mp2也是一样的,各有两个的原因是因为我采用了两次离散化。

	ll res[mx];//每次的结果要用ll来存储,不然就会导致答案不全 
	int temp1[mx];//求"正逆序对"的中转数组,用于后面排序 
	int temp2[mx];//求"反逆序对"的中转数组 
	map<int,int> mp;//用于离散化的map,将数组里的值大小关系转化后才能下标大小关系 
	map<int,int> mp2;

②:将其排序,并用map将数组的值与下标值建立关系。

	//离散化操作 1
	sort(temp1,temp1+n,cmp);
	for(int k = 0;k<n;k++){
		mp[temp1[k]] = k;
	}
	//离散化操作结束 
	-------中间代码----------
	//离散化操作 2
	sort(temp2,temp2+n);
	for(int k=0;k < n;k++){
		mp2[temp2[k]] = k;
	}
	//离散化操作结束

这样其实就已经结束了,离散化。

最后说一下,我这样离散化取逆序对与上个方法的不同点。离散化的方法取反逆序对还是与上面一样,唯一不同的就是正逆序对不太一样。我其实是按照取反逆序对的思路来取正逆序对的,所以两个的代码很像。唯一不同的就是我上面sort两者是不同的。

	for(int j = 0;j < n;j++){//从前往后取出正逆序对 
		res[j] += getsum(mp[a[j]]);
		update(mp[a[j]]+1,1);
	}

	for(int i = n-1;i >= 0;i--){//从后往前取出反逆序对的操作 
		res[i] += getsum(mp2[a[i]]);
		update(mp2[a[i]]+1,1);
	}

方法二的ac代码:

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

const int mx = 100005;
int c[mx];
int a[mx];


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

void update(int i,int k){

	while(i < mx){
		c[i] += k;
		i += lowbit(i);
	}
}

int getsum(int i){
	int ans = 0;
	while(i > 0){
		ans += c[i];
		i -= lowbit(i);
	}
	return ans;
}

bool cmp(int a,int b){
	return a>b;
}

int main(){
	int n;
	ll res[mx];//每次的结果要用ll来存储,不然就会导致答案不全 
	int temp1[mx];//求"正逆序对"的中转数组,用于后面排序 
	int temp2[mx];//求"反逆序对"的中转数组 
	map<int,int> mp;//用于离散化的map,将数组里的值大小关系转化后才能下标大小关系 
	map<int,int> mp2;
	
	cin>>n;
	
	memset(c,0,sizeof(c));
	memset(res,0,sizeof(res));
	memset(temp1,0,sizeof(temp1));
	memset(temp2,0,sizeof(temp2));
	
	for(int i = 0;i < n;i++){//接受数组数据 
		cin>>a[i];
		temp1[i] = a[i];
		temp2[i] = a[i];
	}
	//离散化操作 1
	sort(temp1,temp1+n,cmp);
	for(int k = 0;k<n;k++){
		mp[temp1[k]] = k;
	}
	//离散化操作结束 
	for(int j = 0;j < n;j++){//从前往后取出正逆序对 
		res[j] += getsum(mp[a[j]]);
		update(mp[a[j]]+1,1);
	}
	
	
	memset(c,0,sizeof(c));
	//离散化操作 2
	sort(temp2,temp2+n);
	for(int k=0;k < n;k++){
		mp2[temp2[k]] = k;
	}
	//离散化操作结束
	
	for(int i = n-1;i >= 0;i--){//从后往前取出反逆序对的操作 
		res[i] += getsum(mp2[a[i]]);
		update(mp2[a[i]]+1,1);
	}
	
	ll ans = 0;
	for(int i = 0;i < n;i++){//统计所有小朋友的不高兴值 
//		cout<<res[i]<<" ";

		ans += (ll)((1+res[i])*res[i])/2;
		
	}
//	cout<<endl;
	cout<<ans<<endl;
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值