小朋友排队(树状数组)——第五届蓝桥杯省赛C++B/C组

小朋友排队(树状数组)

n 个小朋友站成一排。

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

每个小朋友都有一个不高兴的程度。

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

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

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

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

输入格式
输入的第一行包含一个整数 n,表示小朋友的个数。

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

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

数据范围
1≤n≤100000,
0≤Hi≤1000000
输入样例:
3
3 2 1
输出样例:
9
样例解释
首先交换身高为3和2的小朋友,再交换身高为3和1的小朋友,再交换身高为2和1的小朋友,每个小朋友的不高兴程度都是3,总和为9。

题解分析
此题需要求他们的不高兴程度之和最小是多少,每个小朋友的第k次交换,不高兴的程度都会增加 k ,因此,每个小朋友如果交换 k 次到达正确位置,总的不高兴程度为1+2+3+…+k = (1+k) *k /2
那么此题应该让每个小朋友的交换次数达到最小,然后总的不高兴程度就是答案。

首先我们要先知道逆序对的概念:如果前一个数比后一个数大(可以不相邻)的话两个数可以构成一个逆序对。一个序列如果逆序对数量为0,则这个序列呈非降序排列,是有序的,符合题意的从低到高排序。
因此每次交换我们应该进行有意义的交换,意思是每次交换必须使得逆序对数量减少1,如果进行了无意义的交换,那么每个小朋友的交换次数就不是最小,达不到最优,最终的结果应该是逆序对数量消为 0

设某个小朋友下标为 t
序列总的逆序对数量 = 每个小朋友逆序对数量总和
每 个 小 朋 友 理 论 上 的 最 小 交 换 次 数 = 与 这 个 小 朋 友 构 成 的 逆 序 对 数 量 = 序 列 [ 1 , t − 1 ] 中 比 他 高 的 小 朋 友 个 数 + 序 列 [ t + 1 , n ] 中 比 这 个 小 朋 友 低 的 小 朋 友 个 数 。 每个小朋友理论上的最小交换次数 = 与这个小朋友构成的逆序对数量 = 序列[1,t-1]中比他高的小朋友个数 + 序列 [ t+1, n] 中比这个小朋友低的小朋友个数。 ==[1,t1]+[t+1,n]

证明:
我们求出每个小朋友的最小交换次数,求不高兴程度加起来就是最小的,是答案。
每个小朋友都取最小交换次数是可以取得的,因为冒泡排序就是采用了这样的思想使得每次循环都让一个数的逆序对降为 0 。结论得证。

解法:
使用树状数组存储每个数出现的次数,边遍历边维护树状数组,看代码理解吧
也可使用贪心 和 归并排序做。

#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdio>
using namespace std;
typedef long long LL;

const int N = 1000005;
int h[N],tree[N],sum[N]; //tree[i]树状数组存储每个数出现的次数

int lowbit(int x){return x&(-x);}
void add(int x,int v){
    for(int i=x; i<N; i+=lowbit(i)){
        tree[i] += v;
    }
}
int query(int x)
{
    int ans = 0;
    for(int i=x; i>0; i-=lowbit(i)){
        ans += tree[i];
    }
    return ans;
}

int main()
{
    int n;
    scanf("%d", &n);
    for(int i=1; i<=n; ++i){
        scanf("%d", &h[i]);
        h[i]++;
    }
    //求每个学生前面比他高的人数
    for(int i=1; i<=n; ++i){
        sum[i] = query(N-1) - query(h[i]);
        add(h[i],1);
    }
    memset(tree,0,sizeof tree);
    //求每个学生后面比他低的人数
    for(int i=n; i>=1; --i){
        sum[i] += query(h[i]-1);
        add(h[i],1);
    }
    LL ans = 0;
    for(int i=1; i<=n; ++i){
        ans += (LL)(1+sum[i])*sum[i]/2;
    }
    printf("%lld\n",ans);
    return 0;
}
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

葛济维的博客

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

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

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

打赏作者

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

抵扣说明:

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

余额充值