题解:洛谷 P17010 排排坐
【题目来源】
洛谷:P17010 [GESP202606 五级] 排排坐 - 洛谷
【题目描述】
老师正在和小朋友们分糖果。
小朋友们先在自己的手上写一个数字,然后坐成一排。
老师分发糖果的规则是:每个小朋友获得自己以及左侧所有小朋友的手上数字之和个糖果。
现在小朋友们都已经在自己手上写上了数字。
请帮小朋友们安排合适的座位顺序,使得小朋友们分到的糖果总量最大,输出这个最大值。
【输入】
输入 \(2\) 行,
第一行为一个正整数 \(n\),表示小朋友的个数;
第二行为 \(n\) 个正整数 \(a_1, a_2, \cdots, a_n\),表示小朋友们手上的数字,整数之间以空格分隔。
【输出】
输出一个整数,表示小朋友们可能分到的最大糖果总数量。
【输入样例】
5
7 5 8 9 3
【输出样例】
111
【核心思想】
-
问题分析:给定 \(n\) 个正整数 \(a_1, a_2, \ldots, a_n\),需要安排座位顺序使得糖果总量最大。第 \(i\) 个座位的小朋友获得糖果数为前 \(i\) 个位置数字之和,总糖果量为所有前缀和之和。这是一个贪心排序问题,核心在于确定最优的排列顺序使得前缀和总和最大。
-
算法选择:
- 贪心策略:将较大的数字放在更左侧的位置,使其被累加更多次
- 前缀和累加:排序后计算前缀和,再累加所有前缀和得到总糖果量
-
关键步骤:
- 读入数据:读取 \(n\) 和数组 \(a[1..n]\)
- 贪心排序:将 \(a\) 按降序排列(从大到小),使得大数字尽量靠左
- 前缀和计算:\(sa[i] = sa[i-1] + a[i]\),\(sa[i]\) 表示前 \(i\) 个位置数字之和
- 累加总糖果量:\(ans \leftarrow ans + sa[i]\),第 \(i\) 个位置的小朋友获得 \(sa[i]\) 个糖果
- 输出结果:\(ans\)
-
时间/空间复杂度:
- 时间复杂度:\(O(n \log n)\),排序为 \(O(n \log n)\),前缀和与累加为 \(O(n)\)
- 空间复杂度:\(O(n)\),前缀和数组
-
贪心排序的核心思想:
- 贡献度分析:位置 \(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
【算法真传】
浙公网安备 33010602011771号