[蓝桥杯 2014 省 B] 小朋友排队 - 题解

durianp 技术算法 2024-03-21

[蓝桥杯 2014 省 B] 小朋友排队

1. 题目详情

题目描述

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

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

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

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

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

输入格式

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

第二行包含 $n$ 个整数 $H_1,H_2 \cdots H_n$,分别表示每个小朋友的身高。

输出格式

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

样例 #1

样例输入 #1

3
3 2 1

样例输出 #1

9

提示

【样例说明】

首先交换身高为 $3$ 和 $2$ 的小朋友,再交换身高为 $3$ 和 $1$ 的小朋友,再交换身高为 $2$ 和 $1$ 的小朋友,每个小朋友的不高兴程度都是 $3$,总和为 $9$。

【数据规模与约定】

对于 $10\%$ 的数据,$1 \le n \le 10$;

对于 $30\%$ 的数据,$1 \le n \le 1000$;

对于 $50\%$ 的数据,$1 \le n \le 10000$;

对于 $100\%$ 的数据,$1 \le n \le 100000$,$0 \le H_i \le 1000000$。

时限 1 秒, 256M。蓝桥杯 2014 年第五届省赛

2. 解题思路

这个题目与之前做过的题目火柴排队很容易产生联想,因为同样一次只能交换相邻元素,同时也寻找步数最少的最优解。但火柴排队问题需要求得最少步数,而这个问题需要求得最少步数时每个元素的移动次数,如果硬套用火柴排队中的逆序对来做的话实现起来很麻烦。具体思路为,每个小朋友移动的次数 $t$,等于排在这个小朋友前面但是比这个小朋友高的小朋友总数 $h$ ,与排在这个小朋友后比这个小朋友矮的小朋友数s的和,$t=h+s$ ,也就是以该元素组成的所有逆序对数量。再根据每个 $t$ 求解不开心程度并求和即可。在我看到的题解中,这个思路基本上都使用了高级数据结构线段树,在我尝试使用归并排序实现这个思路时,发现难度很大,各种情况讨论比较复杂,一直没有写出正解,于是提出了一种更易于理解的思路。

归并排序的过程本质就是一个步数最少排序过程,它的每一步都是全局最优的。那么在归并排序的过程中,将排序的每一步都记录下来,就可以获得每个元素的 $t$ ——交换次数了。那么应该在什么地方进行记录呢?在解决每个归并排序的子问题时,元素可能会产生位置变化,而位置变化所需要的交换,在全局来说都是必要且最优的。而元素位置的变化量,就是解决这个子问题时,这个元素产生的必要交换的次数。那么在每个子问题解决完后,记录每个元素的位置改变量并逐子问题累加在一起,即可求得该元素的总交换数。

这个思路过程本质上是进行一次归并排序,时间复杂度为 $O(n\log_n)$ ,计算每个小朋友的不高兴程度用等差数列求和时间复杂度为 $O(n)$ ,因此该算法时间复杂度为 $O(n\log_n)$。

3. 实现代码

// https://www.luogu.com.cn/problem/P8613
// https://www.acwing.com/problem/content/description/1217/
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

typedef long long LL;

struct Child 
{
    int height;
    LL unhappiness;
    int originalIndex;
};

int n;
vector<Child> children;

void merge(int left, int mid, int right) 
{
    int i = left, j = mid + 1;
    vector<Child> temp;
    while (i <= mid && j <= right) 
    {
        if (children[i].height <= children[j].height)
            temp.push_back(children[i++]);
        else
            temp.push_back(children[j++]);
    }
    while (i <= mid) temp.push_back(children[i++]);
    while (j <= right) temp.push_back(children[j++]);
    for (int k = left; k <= right; k++) children[k] = temp[k - left];
}

void mergeSort(int left, int right) 
{
    if (left == right) return;
    int mid = (left + right) >> 1;
    mergeSort(left, mid);
    mergeSort(mid + 1, right);
    merge(left, mid, right);
    
    for (int i = left; i <= right; i++) 
    {
        // 累加位置变化量
        children[i].unhappiness += abs(i - children[i].originalIndex);
        // 重置初始位置
        children[i].originalIndex = i;
    }
}

int main() {

    cin >> n;
    children.resize (n);
    for (int i = 0; i < n; i++) 
    {
        cin >> children[i].height;
        children[i].unhappiness = 0;
        children[i].originalIndex = i;
    }
    
    mergeSort(0, n - 1);
    
    LL totalUnhappiness = 0;
    for (int i = 0; i < n; i++) 
    {
        totalUnhappiness += ((1LL + children[i].unhappiness) * children[i].unhappiness) / 2LL;
    }
    
    cout << totalUnhappiness << endl;
    
    return 0;
}

4. 相关链接

  1. 原题链接:[P8613 [蓝桥杯 2014 省 B] 小朋友排队 - 洛谷](https://www.luogu.com.cn/problem/P8613) 1215. 小朋友排队 - AcWing题库
  2. 相似题目题解:[[NOIP2013 提高组] 火柴排队 - 题解 - durianp - dup.asia](https://www.dup.asia/technique/noip2013-match/)
  3. 逆序对思路使用线段树和树状数组的做法:小朋友排队 - onlyblues - 博客园 (cnblogs.com)
PREV
[NOIP2013 提高组] 火柴排队 - 题解

评论(0)

发布评论