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

题目描述

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

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

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

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

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

输入格式

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

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

输出格式

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

输入输出样例 #1

输入 #1

3
3 2 1

输出 #1

9

说明/提示

【样例说明】

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

【数据规模与约定】

对于 的数据,;

对于 的数据,;

对于 的数据,;

对于 的数据,,。

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

思路

这道题的常见解法就是正常的冒泡或者是选择,如果在一些题目数据卡的比较松的题还是蛮好过的,但是在这道题,很明显这样的解法是不可能的,那么我们就要找其他方法:每次计数的问题,我们可以通过去寻找数列的逆序数,那么在这道题中,我们可以通过两种解法:①树状数组,②归并排序

个人更推荐树状数组,处理方便,更加简洁,归并排序的逻辑可能稍微有点复杂

树状数组

//
// Created by 23264 on 2026/2/11.
//
#include<bits/stdc++.h>
using namespace std;
 
constexpr int MAX_HEIGHT = 1000005;
 
struct Child
{
    int height;
    long long swapNum;
    long long unhappy;
};
 
int n;
vector<Child> child;
vector<int> tree;
 
int LowBit(int x)
{
    return x & -x;
}
 
void Update(int x,int v)
{
    for (int i = x;i < MAX_HEIGHT;i += LowBit(i))
    {
        tree[i] += v;
    }
}
 
int Query(int x)
{
    int res = 0;
    for (int i = x;i > 0;i -= LowBit(i))
    {
        res += tree[i];
    }
    return res;
}
 
void solve()
{
    for (int i = 0;i < n;i++)
    {
        int h = child[i].height + 1;
        child[i].swapNum += (i - Query(h));
        Update(h,1);
    }
    tree.assign(MAX_HEIGHT,0);
    for (int i = n - 1;i >= 0;i--)
    {
        int h = child[i].height + 1;
        child[i].swapNum += Query(h - 1);
        Update(h,1);
    }
 
    for (int i = 0;i < n;i++)
    {
        child[i].unhappy = child[i].swapNum * (child[i].swapNum + 1) / 2;
    }
}
 
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cin >> n;
    child.resize(n);
    tree.resize(MAX_HEIGHT,0);
    for (int i = 0;i < n;i++)
    {
        cin >> child[i].height;
        child[i].unhappy = child[i].swapNum = 0;
    }
    solve();
    long long sum = 0;
    for (int i = 0;i < n;i++)
    {
        sum += child[i].unhappy;
    }
    cout << sum;
}

归并排序

#include<bits/stdc++.h>
using namespace std;
 
struct Child {
    int height;
    int id; // 记录原始下标,以便把次数记在正确的人头上
};
 
int n;
vector<Child> temp;   // 归并排序需要的临时数组
vector<Child> arr;    // 存储小朋友
vector<long long> counter; // 记录每个人的交换次数 (使用 long long 防止溢出)
 
// 归并排序的核心:合并两个有序区间 [l, mid] 和 [mid+1, r]
void merge(int l,int mid,int r)
{
    int i = l;        // 左半边指针
    int j = mid + 1;  // 右半边指针
    int k = l;        // 临时数组指针
 
    while (i <= mid && j <= r)
    {
        if (arr[i].height <= arr[j].height)
        {
            // 左边的数小(或相等),放入临时数组
            // 此时,右半边已经有 (j - (mid + 1)) 个数被移走了
            // 这些被移走的数都比 arr[i] 小,且原本在 arr[i] 后面
            // 所以 arr[i] 的交换次数要加上这些数的数量
            counter[arr[i].id] += (j - (mid + 1));
            temp[k++] = arr[i++];
        }
        else
        {
            // 右边的数小,放入临时数组
            // 此时,左半边剩下的 (mid - i + 1) 个数都比 arr[j] 大
            // arr[j] 需要跨过这些数移到前面去
            // 所以 arr[j] 的交换次数要加上左半边剩余的数量
            counter[arr[j].id] += (mid - i + 1);
 
            temp[k++] = arr[j++];
        }
    }
 
    // 处理剩余的左半边
    while (i <= mid)
    {
        // 同理,剩下的这些数,都要跨过右半边所有的数(即 r - mid 个)
        // 也就是 j 此时已经跑到了 r+1,所以是 (j - (mid+1))
        counter[arr[i].id] += (j - (mid + 1));
        temp[k++] = arr[i++];
    }
 
    // 处理剩余的右半边
    while (j <= r)
    {
        // 右边剩下的数不用统计逆序,因为左边都走完了,它们位置相对没变
        temp[k++] = arr[j++];
    }
 
    // 将临时数组倒回原数组
    for (int p = l;p <= r;p++)
    {
        arr[p] = temp[p];
    }
}
 
// 递归分治
void mergeSort(int l,int r)
{
    if (l >= r)
    {
        return;
    }
    int mid = (l + r) / 2;
    mergeSort(l,mid);
    mergeSort(mid + 1,r);
    merge(l,mid,r);
}
 
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cin >> n;
    arr.resize(n);
    temp.resize(n);
    counter.resize(n,0);
    for (int i = 0;i < n;i++)
    {
        cin >> arr[i].height;
        arr[i].id = i; // 记住原始位置
    }
 
    // 开始归并排序
    mergeSort(0,n - 1);
 
    long long totalUnhappy = 0;
 
    // 计算总不高兴程度
    for (int i = 0;i < n;i++)
    {
        // 每个人总共交换了 counter[i] 次
        // 利用公式 k*(k+1)/2 计算不高兴程度
        long long k = counter[i];
        totalUnhappy += k * (k + 1) / 2;
    }
    cout << totalUnhappy << endl;
}