喵哈喽~!各位看官,今天由我,小猫娘,来为大家讲解这道有趣的题目哦!这道题叫做「250 Thousand Tons of TNT」,听起来就很厉害的样子,对吧?不过别担心,只要跟着本喵的思路走,再难的题目也会变得像撸猫一样简单的说!
题目大意
简单来说呢,就是有一个叫 Alex 的人,他有 n 个箱子排成一排,每个箱子都有自己的重量 a_i 嘛。他想用很多辆卡车把这些箱子运走。
所有的卡车都一样大,每辆车都得装 k 个箱子。装箱的规则是:
- 第一辆车装前
k个箱子。 - 第二辆车装接下来
k个箱子。 - ...以此类推,直到所有箱子都被装完。
这就要求 n 必须能被 k 整除才行哦!
Alex 这个人呢,有点小坏心眼,他希望所有卡车里,装得最重的和装得最轻的,它们之间的重量差越大越好!他可以自由选择 k 的值(只要 k 能整除 n),所以我们的任务就是帮他找到一个 k,让这个最大的重量差变得最大,然后把这个差值告诉他。
如果只有一个卡车(也就是 k=n),那差值就是 0 啦~ 听明白了吗喵?
题解方法
这道题的核心就是要找到一个最合适的 k 值嘛。那我们该怎么找呢?
首先,我们得明白 k 必须满足什么条件。因为每辆车都要装 k 个箱子,而且所有 n 个箱子都要被装完,所以 n 必须能被 k 整除。也就是说,k 必须是 n 的一个因子(或者叫约数)的说。
所以呀,我们的问题就变成了:遍历所有 n 的因子 k(除了 k=n 本身,因为 k=n 时只有一个卡车,差值是 0),对于每个 k,计算出对应的最大重量差,然后在所有这些差值里找一个最大的!
那么,具体要怎么做呢喵?
找到所有因子:一个一个试太慢啦!我们可以用一个聪明的方法:从 1 遍历到
sqrt(n)。如果i是n的因子,那么n/i也一定是n的因子。这样我们一次就能找到一对因子,效率高多啦!快速计算每辆卡车的总重量:对于一个确定的
k,我们需要计算n/k辆卡车的重量。第一辆是a_1到a_k的和,第二辆是a_{k+1}到a_{2k}的和…… 如果每次都重新加一遍,会很慢的。这时候,我们的好朋友「前缀和」就登场啦!- 我们可以先预处理一个前缀和数组
prefix_sum,其中prefix_sum[i]表示前i个箱子的总重量。 - 这样,第
j辆卡车(装的是从(j-1)*k+1到j*k的箱子)的重量就可以用prefix_sum[j*k] - prefix_sum[(j-1)*k]瞬间算出来!是不是很神奇喵?
- 我们可以先预处理一个前缀和数组
计算并更新答案:对于每个因子
k,我们用前缀和快速算出所有卡车的重量,然后找到其中的最大值max_weight和最小值min_weight。它们的差max_weight - min_weight就是当前k对应的答案。我们用一个变量max_abs_diff来记录所有k算出来的差值中的最大值。
把所有因子都试一遍之后,max_abs_diff 就是我们最终的答案啦!
题解
这是本喵写的 C++ 代码,大家可以参考一下哦~
#include <iostream>
#include <vector>
#include <numeric>
#include <algorithm>
#include <cmath>
#include <limits>
// 这是一个帮助我们计算特定k值下重量差的函数哦
long long calculate_diff(int n, int k, const std::vector<long long>& prefix_sum) {
long long min_weight = std::numeric_limits<long long>::max(); // 先把最小重量设成一个超大的数
long long max_weight = std::numeric_limits<long long>::min(); // 最大重量设成一个超小的数
// 遍历每一辆卡车~
for (int i = 1; i <= n / k; ++i) {
// 用前缀和快速算出第i辆卡车的重量!
long long current_weight = prefix_sum[i * k] - prefix_sum[(i - 1) * k];
min_weight = std::min(min_weight, current_weight); // 更新最小重量
max_weight = std::max(max_weight, current_weight); // 更新最大重量
}
return max_weight - min_weight; // 返回最大和最小的差值
}
void solve() {
int n;
std::cin >> n;
std::vector<long long> a(n);
for (int i = 0; i < n; ++i) {
std::cin >> a[i];
}
// 预处理前缀和数组,喵~
// prefix_sum[i] 表示前 i 个箱子的总重量
std::vector<long long> prefix_sum(n + 1, 0);
for (int i = 0; i < n; ++i) {
prefix_sum[i + 1] = prefix_sum[i] + a[i];
}
long long max_abs_diff = 0; // 用来记录最终答案的变量
// 遍历 n 的所有因子 k
// 我们只用遍历到 sqrt(n) 就好啦,因为如果 i 是因子,n/i 也是因子
for (int k = 1; k * k <= n; ++k) {
if (n % k == 0) { // 找到了一个因子 k!
// 处理因子 k
if (k < n) { // k=n 的情况只有一个卡车,差值是0,不用管
max_abs_diff = std::max(max_abs_diff, calculate_diff(n, k, prefix_sum));
}
// 处理另一个因子 n/k
int other_k = n / k;
if (other_k != k && other_k < n) { // 同样,k=n的情况不用管,也要避免和k重复计算
max_abs_diff = std::max(max_abs_diff, calculate_diff(n, other_k, prefix_sum));
}
}
}
std::cout << max_abs_diff << "\n";
}
int main() {
// 加速输入输出,让程序跑得更快一点~
std::ios_base::sync_with_stdio(false);
std::cin.tie(NULL);
int t;
std::cin >> t;
while (t--) {
solve();
}
return 0;
}知识点介绍
为了让大家更好地理解这个解法,本喵来给大家科普一下里面用到的两个小技巧吧!
1. 前缀和 (Prefix Sums) 喵~
前缀和是一种非常实用的小技巧,用来快速计算一个序列(比如我们的箱子重量数组)中任意一段区间的和。它的思想很简单:
- 是什么: 创建一个新数组,我们叫它
prefix_sum。prefix_sum[i]存储的是原数组从第 0 个元素到第i-1个元素的总和。 - 怎么算:
prefix_sum[0] = 0,然后prefix_sum[i] = prefix_sum[i-1] + a[i-1]。递推一下就好啦! - 怎么用: 想要计算原数组中从下标
L到R(包含L和R)这一段的和,只需要用prefix_sum[R+1] - prefix_sum[L]就可以了!比如我们这道题里,第i辆卡车(1-indexed)装的是(i-1)*k到i*k-1的箱子,它的重量就是prefix_sum[i*k] - prefix_sum[(i-1)*k]。是不是超级方便的说!
2. 高效找因子 (Finding Divisors Efficiently) 嘛
当我们要找一个数 n 的所有因子时,最笨的办法是从 1 遍历到 n,看谁能整除 n。但如果 n 很大,这就太慢了。
一个更聪明的办法是只遍历到 sqrt(n)(n 的平方根)。
- 为什么可以: 因为因子总是成对出现的!如果
i是n的一个因子,那么n/i也必然是n的一个因子。举个例子,对于n=12,2是它的因子,那么12/2 = 6也是因子。3是因子,12/3 = 4也是因子。 - 怎么做: 我们从
i = 1循环到i * i <= n。在循环里,如果i能整除n(即n % i == 0),我们就找到了一个因子i。同时,我们也找到了它的搭档n/i!这样一来,我们只需要检查sqrt(n)那么多次,就能找到所有的因子了,速度快了很多很多哦!- 注意:如果
n是一个完全平方数,比如 36,当i=6时,n/i也是 6,这时候它们是同一个因子,不要重复计算就好啦。
- 注意:如果
好啦,今天的讲解就到这里啦~ 是不是很简单呢?只要把问题分解开,找到合适的工具(比如前缀和和找因子的技巧),问题就迎刃而解了!希望本喵的讲解对大家有帮助哦!下次再见啦,喵~