题目链接
题目描述
给定一个长度为 的序列
。将这个序列切割一刀,划分为两个非空的子序列。切割的代价定义为两个子序列元素和的乘积。求最小的切割代价。
解题思路
本题要求找到一个切割点,使得左右两边子序列元素和的乘积最小。
假设我们在第 个元素之后进行切割(
)。
- 左边的子序列是
。
- 右边的子序列是
。
设左边子序列的和为
。
设右边子序列的和为 。
那么,在第 个元素后切割的代价就是
。
我们的目标就是找到一个 (从 1 到
),使得
最小。
一个朴素的想法是,遍历所有可能的切割点 (from 1 to
):
- 对于每个
,计算左边和
。
- 计算右边和
。
- 计算乘积,并与当前记录的最小代价进行比较。
这样做的时间复杂度是 ,因为每次计算子序列和都需要
的时间。
为了优化计算,我们可以使用前缀和。
首先,计算整个序列的总和 。
然后,我们再次遍历所有可能的切割点
:
- 在遍历过程中,我们可以维护一个变量
left_sum
,表示当前左边子序列的和。 - 当我们从切割点
移动到
时,
left_sum
只需要加上即可。
- 右边子序列的和可以直接通过
right_sum = total_sum - left_sum
得到。 - 计算代价
left_sum * right_sum
,并更新全局的最小代价。
通过这种方式,我们可以在一次遍历中计算出所有切割点的代价,总时间复杂度降为 。
算法步骤:
-
计算整个数组的总和
。
-
初始化
min_cost
为一个非常大的数(例如LLONG_MAX
)。 -
初始化
left_sum
为 0。 -
遍历数组(从
到
,对应切割点在
到
之后):
a. 更新
left_sum
,加上当前元素。
b. 计算
right_sum = total_sum - left_sum
。c. 计算当前代价
cost = left_sum * right_sum
。d. 更新
min_cost = min(min_cost, cost)
。 -
输出
min_cost
。
代码
#include <iostream>
#include <vector>
#include <numeric>
#include <algorithm>
#include <limits>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(NULL);
int n;
cin >> n;
vector<long long> a(n);
long long total_sum = 0;
for (int i = 0; i < n; ++i) {
cin >> a[i];
total_sum += a[i];
}
long long min_cost = numeric_limits<long long>::max();
long long left_sum = 0;
// 遍历 n-1 个可能的切割点
for (int i = 0; i < n - 1; ++i) {
left_sum += a[i];
long long right_sum = total_sum - left_sum;
min_cost = min(min_cost, left_sum * right_sum);
}
cout << min_cost << "\n";
return 0;
}
import java.util.Scanner;
import java.lang.Math;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
long[] a = new long[n];
long totalSum = 0;
for (int i = 0; i < n; i++) {
a[i] = sc.nextLong();
totalSum += a[i];
}
long minCost = Long.MAX_VALUE;
long leftSum = 0;
// 遍历 n-1 个可能的切割点
for (int i = 0; i < n - 1; i++) {
leftSum += a[i];
long rightSum = totalSum - leftSum;
minCost = Math.min(minCost, leftSum * rightSum);
}
System.out.println(minCost);
}
}
import sys
def main():
n = int(sys.stdin.readline())
a = list(map(int, sys.stdin.readline().split()))
total_sum = sum(a)
min_cost = float('inf')
left_sum = 0
# 遍历 n-1 个可能的切割点
for i in range(n - 1):
left_sum += a[i]
right_sum = total_sum - left_sum
min_cost = min(min_cost, left_sum * right_sum)
print(min_cost)
if __name__ == "__main__":
main()
算法及复杂度
- 算法:前缀和思想优化遍历
- 时间复杂度:
。需要一次遍历计算总和,再一次遍历寻找最小代价。
- 空间复杂度:
,用于存储输入的数组。如果可以边读边计算总和,空间复杂度可以优化到
(不考虑输入数组本身)。