您的位置:首页 > 其它

蓝桥杯历届试题 小朋友排队(线段树+逆序数的理解)

2017-09-30 22:03 363 查看
题目描述:有n个小朋友站成一排,要求你按身高由低到高的顺序对小朋友排队。你只能交换相邻的小朋友,并且每次每次小朋友被第m次交换,他们对应的不满意度会提高m。要求你能达到的最小不满意度。(1<=n<=100000,0<=a[i]<=1000000)
题目补充
1)对n个人按身高排队,每次只能交换相邻的人,求最少交换次数。
理解:即逆序数。先对最大的数操作,它后面的数都能和它构成逆序对(与它相等的数除外),交换的次数就是逆序对的数量,同理再最次大的数操作,依次递推。
2)对n个人按身高排队,每次能交换任意的两人,求最少交换次数。
理解:这个比较简单,先去掉已经站好队的人(有a个),再去掉所有正好站反次序的两人(有b个),剩下的(有n-a-b)经过n-a-b次交换可归位。所以共交换n-a-b/2次。
此题思路
根据补充1,可得每个小朋友的交换次数cnt=左边比他高的人的数量+右边比他矮的人的数量。可通过正反两次求逆序数求得。
线段树思考:折腾了一下午,也总算ac了。目前来说,线段树的作用在于单点更新,区间求和,求最大最小值。这题就用到了区间求和的思想。知道思路后,要明确求和对象,建立与之对应的线段树(开始写的时候总想往a数组上扯,这是不对的)。求一个数前面比他小的数的数量,“乒乓比赛”那篇博文用树状数组实现了,此题与之雷同。
树状数组求逆序数:http://blog.csdn.net/mungbeanwithredface/article/details/77899361

ac代码:#include<bits/stdc++.h>
#define maxn 1000000
using namespace std;
typedef long long ll;

int sum[4*maxn];
int sum1[100001],a[100001];
void push_up(int rt){
sum[rt]=sum[rt<<1]+sum[rt<<1|1];
}
void build(int l,int r,int rt){
if(l==r){
sum[rt]=0;
return;
}
int mid=(l+r)>>1;
build(l,mid,rt<<1);
build(mid+1,r,rt<<1|1);
push_up(rt);
}
void update(int p,int x,int l,int r,int rt){
if(l==r){
sum[rt]+=x;
return;
}
int mid=(l+r)>>1;
if(p<=mid) update(p,x,l,mid,rt<<1);
else update(p,x,mid+1,r,rt<<1|1);
push_up(rt);
}
int query(int l,int r,int L,int R,int rt){
if(l>=L&&r<=R){
return sum[rt];
}
int mid=(l+r)>>1,t=0;
if(L<=mid) t+=query(l,mid,L,R,rt<<1);
if(R>mid) t+=query(mid+1,r,L,R,rt<<1|1);
return t;
}
int main(){
int n;
while(~scanf("%d",&n)){
memset(sum1,0,sizeof(sum1));
build(0,maxn-1,1);
for(int i=0;i<n;i++) scanf("%d",a+i);
for(int i=0;i<n;i++){
sum1[i]+=query(0,maxn-1,a[i]+1,maxn-1,1);
update(a[i],1,0,maxn-1,1);
}
build(0,maxn-1,1);
for(int i=n-1;i>=0;i--){
if(a[i]) sum1[i]+=query(0,maxn-1,0,a[i]-1,1);
update(a[i],1,0,maxn-1,1);
}
ll s=0;
for(int i=0;i<n;i++){
s+=(ll)sum1[i]*(sum1[i]+1)/2;
}
printf("%I64d\n",s);
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: