标题:小朋友排队(本质:逆序对,可用归并排序,树状数组)

解决小朋友按身高排队的问题,通过计算逆序对数确定最少不高兴程度总和,采用归并排序和树状数组优化算法。


标题:小朋友排队

    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。


资源约定:
峰值内存消耗 < 256M
CPU消耗  < 1000ms


请严格按要求输出,不要画蛇添足地打印类似:“请您输入...” 的多余内容。

所有代码放在同一个源文件中,调试通过后,拷贝提交该源码。

注意: main函数需要返回0
注意: 只使用ANSI C/ANSI C++ 标准,不要调用依赖于编译环境或操作系统的特殊函数。
注意: 所有依赖的函数必须明确地在源文件中 #include <xxx>, 不能通过工程设置而省略常用头文件。

提交时,注意选择所期望的编译器类型。
 

归并排序: 

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#define ref(i,s,e,c) for(int i=s;i<e;i=i+c)  //[s,e)
#define ref1(i,e,s,c) for(int i=e;i>=s;i=i-c)  //[e,s]
#define ref2(i,s,e,c) for(int i=s;i<=e;i=i+c)  //[s,e]
using namespace std;
typedef long long ll;
const int maxn = 100000+5;
ll anger[maxn];
ll sum=0;
struct node{
	int id;
	ll num;
	bool operator<(const node& temp) const{
		return this->num<temp.num;
	}
}h[maxn];
/*
某同学的不开心度和交换次数有关,即和逆序对有关
又因为每次交换双方都会增加不开心度,所以该数前面比它大的,后面比它小的都要算一次 
*/
void mergesort(int ns,int ne){ 
	if(ne-ns==1) return;
	int m=(ne+ns)>>1;
	mergesort(ns,m);
	mergesort(m,ne);
	//node *ta=new node[ne-ns];
	int nl=ns,nr=m;//前面大于的情况 ,从小到大 
	while(nl<m&&nr<ne){
		if(h[nl].num<=h[nr].num){
			nl++;
		}
		else{
			anger[h[nr].id]+=(ll)(m-nl);
			nr++;
		}
	}
	nl=m-1;//后面小于的情况 ,从大到小
	nr=ne-1; 
	while(nl>=ns&&nr>=m){
		if(h[nl].num>h[nr].num){
			anger[h[nl].id]+=(ll)(nr-m+1);
			nl--;
		}
		else{
			nr--;
		}
	}
	sort(h+ns,h+ne);
	return;
}
int main(){
	int n;
	scanf("%lld",&n);
	ref(i,0,n,1){
		scanf("%lld",&h[i].num);
		h[i].id=i;
	}
	mergesort(0,n);
	ref(i,0,n,1){
		//cout<<anger[i]<<"\n";
		sum+=(anger[i]+1)*anger[i]/2;
	}
	printf("%lld",sum);
	return 0; 
}

 

树状数组:

原理、功能等介绍:https://blog.youkuaiyun.com/bestsort/article/details/80796531 

 

一:树状数组求逆序对

求逆序对原理:https://blog.youkuaiyun.com/ssimple_y/article/details/53744096

#include<bits/stdc++.h>
using namespace std;
const int MAX=100000;
int c[MAX];
struct node{
	int value,sub;
	bool operator<(const node& temp) const{
		return this->value<temp.value;
	}
}arr[MAX];
int lowerbit(int x){
	return x&(-x);
}
void update(int x,int y,int n){
	for(int i=x;i<=n;i+=lowerbit(i)){
		c[i]+=y;
	}
}
int getsum(int x){
	int sum=0;
	for(int i=x;i;i-=lowerbit(i)){
		sum+=c[i];
	}
	return sum;
}
int main(){
	int n;
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>arr[i].value;
		arr[i].sub=i;
	}
	sort(arr+1,arr+n+1);
	int ans=0;
	for(int i=1;i<=n;i++){
		update(arr[i].sub,1,n);
		ans+=i-getsum(arr[i].sub);
	}
	cout<<ans;
	return 0;
}

本题树状数组解法:

大部分借鉴别人的代码

#include <bits/stdc++.h>
#define ref1(i,s,e,c) for(int i=s;i<e;i=i+c)
#define ref2(i,s,e,c) for(int i=s;i<=e;i=i+c)
#define ref3(i,e,s,c) for(int i=e;i>=s;i=i-c)
using namespace std;
typedef long long ll;
//分析可知每个小朋友的交换次数即每个数左边更大和右边更小的数的个数和,即左边与其形成的逆序对数 + 右边与其形成的逆序对数
//则此问题转换为求逆序对数,如果直接暴力求解时间复杂度o(n^2)会超限,则利用树状数组
const int MAXSIZE=100005;
int bit[MAXSIZE];
struct node{
	int val;
	int pos;
}arr[MAXSIZE];
int n;
int lowerbit(int t){
	return t&(-t);
}
void update(int x,int y){
	for(int i=x;i<=n;i+=lowerbit(i)){
		bit[i]+=y;
	}
}
int getsum(int x){
	int sum=0;
	for(int i=x;i;i-=lowerbit(i)){
		sum+=bit[i];
	}
	return sum;
}
bool cmp(const node& a,const node& b){
	return a.val<b.val;
}
bool cmp1(const node& a,const node& b){
	return a.pos<b.pos;
}
ll total[MAXSIZE],b[MAXSIZE];
//由题0<=Hi<=1000000,如果直接建立树状数组会消耗大量空间,可以先把输入的数据变成从1到n,从而节省空间,用到离散化处理
int main()
{
	ref2(i,1,MAXSIZE,1){
		total[i]=i+total[i-1];
	}
	cin>>n;
	ref2(i,1,n,1){
		int height;
		cin>>height;
		arr[i].val=height+1;
		arr[i].pos=i;
	}
	sort(arr+1,arr+1+n,cmp);//先进行一次按val从小到大排序,然后分配1~n,如果有重复可能不到n
	int no=1,temp;
	ref2(i,1,n,1){//把输入的数据变成从1到n,注意重复数的处理 
		if(arr[i].val!=temp&&i!=1){
			no++;
		}
		temp=arr[i].val;
		arr[i].val=no;
	}
//按照pos再从小到大排序,变回原来的样子,此时val的大小比较跟输入时一样,但范围已经缩小到n以内,即离散化处理
	sort(arr+1,arr+1+n,cmp1);
	ll ans=0;
	ref2(i,1,n,1){//计算每个数左边与其形成的逆序对数 
		update(arr[i].val,1);//此时y=1
/*
每次update更新后马上getsum,getsum得到的是[1,arr[i].val]范围内bit的合,因为上面y=1,所以得到的就是输入到目前为止前面小于等于arr[i].val的个数(即左边小于等于该数的个数),而i代表目前总共i个数,减去之后就是左边大于该数的个数
*/
		b[i]+=i-getsum(arr[i].val);
	}
	memset(bit,0,sizeof(bit));
	ref3(i,n,1,1){//计算每个数右边与其形成的逆序对数 ,上同
		update(arr[i].val,1);
		b[i]+=getsum(arr[i].val-1);//因为这里求的是右边小于该数的个数,所以val要-1,去掉自己
	}
	ref2(i,1,n,1){
		ans+=total[b[i]];
	} 
	cout<<ans;
	return 0;
}

本题代码:

题解:https://blog.youkuaiyun.com/wr132/article/details/43856905

https://blog.youkuaiyun.com/smithstf/article/details/79919882

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值