题解:洛谷 P17010 排排坐

【题目来源】

洛谷:P17010 [GESP202606 五级] 排排坐 - 洛谷

【题目描述】

老师正在和小朋友们分糖果。

小朋友们先在自己的手上写一个数字,然后坐成一排。

老师分发糖果的规则是:每个小朋友获得自己以及左侧所有小朋友的手上数字之和个糖果。

现在小朋友们都已经在自己手上写上了数字。

请帮小朋友们安排合适的座位顺序,使得小朋友们分到的糖果总量最大,输出这个最大值。

【输入】

输入 \(2\) 行,

第一行为一个正整数 \(n\),表示小朋友的个数;

第二行为 \(n\) 个正整数 \(a_1, a_2, \cdots, a_n\),表示小朋友们手上的数字,整数之间以空格分隔。

【输出】

输出一个整数,表示小朋友们可能分到的最大糖果总数量。

【输入样例】

5
7 5 8 9 3

【输出样例】

111

【核心思想】

  1. 问题分析:给定 \(n\) 个正整数 \(a_1, a_2, \ldots, a_n\),需要安排座位顺序使得糖果总量最大。第 \(i\) 个座位的小朋友获得糖果数为前 \(i\) 个位置数字之和,总糖果量为所有前缀和之和。这是一个贪心排序问题,核心在于确定最优的排列顺序使得前缀和总和最大。

  2. 算法选择

    • 贪心策略:将较大的数字放在更左侧的位置,使其被累加更多次
    • 前缀和累加:排序后计算前缀和,再累加所有前缀和得到总糖果量
  3. 关键步骤

    • 读入数据:读取 \(n\) 和数组 \(a[1..n]\)
    • 贪心排序:将 \(a\) 按降序排列(从大到小),使得大数字尽量靠左
    • 前缀和计算\(sa[i] = sa[i-1] + a[i]\)\(sa[i]\) 表示前 \(i\) 个位置数字之和
    • 累加总糖果量\(ans \leftarrow ans + sa[i]\),第 \(i\) 个位置的小朋友获得 \(sa[i]\) 个糖果
    • 输出结果\(ans\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(n \log n)\),排序为 \(O(n \log n)\),前缀和与累加为 \(O(n)\)
    • 空间复杂度:\(O(n)\),前缀和数组
  5. 贪心排序的核心思想

    • 贡献度分析:位置 \(i\) 的数字 \(a_i\) 对总糖果量的贡献为 \(a_i \times (n - i + 1)\),即该数字会被包含在从位置 \(i\) 到位置 \(n\) 的所有前缀和中,共被累加 \((n - i + 1)\)
    • 交换论证:假设存在相邻位置 \(i\)\(i+1\) 满足 \(a_i < a_{i+1}\),交换后总贡献增加 \((a_{i+1} - a_i) > 0\),因此降序排列为最优
    • 前缀和优化:不必单独计算每个位置的贡献系数,直接累加前缀和即可得到总糖果量,因为 \(\sum_{i=1}^{n} sa[i] = \sum_{i=1}^{n} a_i \times (n - i + 1)\)
    • 正整数保证:题目保证所有数字为正整数,因此大数字放左侧一定更优,无需考虑负数或零的特殊情况
    • 适用于元素贡献与位置相关的最优排列类问题,核心是通过贡献度分析确定贪心策略

【算法标签】

普及- #前缀和

【代码详解】

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1005;                // 常量:最大小朋友数量

int n;                             // n: 小朋友个数
int ans;                           // ans: 最大糖果总数量
int a[N];                          // a[i]: 第 i 个小朋友手上的数字
int sa[N];                         // sa[i]: 前 i 个小朋友数字的前缀和

signed main()
{
    cin >> n;                      // 读入小朋友个数

    for (int i = 1; i <= n; i++)  // 读入 n 个小朋友手上的数字
        cin >> a[i];

    sort(a + 1, a + n + 1, greater<int>());  // 按数字从大到小排序,让大的数字尽量靠左

    for (int i = 1; i <= n; i++)  // 计算最大糖果总量
    {
        sa[i] = sa[i - 1] + a[i];  // 计算前 i 个小朋友数字的前缀和
        ans += sa[i];              // 第 i 个小朋友分到的糖果数 = 前 i 个数字之和,累加到总量
    }

    cout << ans << endl;           // 输出最大糖果总数量

    return 0;
}

【运行结果】

5
7 5 8 9 3
111

【算法真传】

从一道GESP真题出发:聊聊贪心排序与前缀和优化

posted @ 2026-07-07 09:28  团爸讲算法  阅读(47)  评论(0)    收藏  举报