一、真题描述
有一个序列 a1,a2,...,ana1,a2,...,an , 牛牛将对这个序列切割一刀(划分分成两个不相交的非空序列,一个序列为 a1,…,apa1,…,ap,另一个序列为 ap+1,…,anap+1,…,an),牛牛切割的代价为两个序列元素和的乘积。牛牛想知道切割代价最小是多少。
时间限制:C/C++ 1秒,其他语言2秒
空间限制:C/C++ 256M,其他语言512M
示例1:
输入例子:5
1 2 3 4 5
输出例子:14
例子说明:序列被划分为1 和 2 3 4 5,右边和为 14。
示例2:
输入例子:4
2 1 3 4
输出例子:16
例子说明:序列被划分为 2 和 1 3 4。
二、解题思路&代码实现
1、解题思路:
(1) 前缀和优化计算
为了高效地求出每种切割方式下的两段和,我们可以使用 前缀和数组 prefixSum[]。
定义:
prefixSum[i] = a[0] + a[1] + ... + a[i - 1]
这样对于任意的 p
,可以快速得到:
sum1 = prefixSum[p + 1]
sum2 = prefixSum[n] - sum1
(2)遍历所有合法切割点
从 p = 0
到 p = n - 2
(即切割点在第一个元素之后、最后一个元素之前),依次计算每个切割点的代价,并记录最小值。
2、时间复杂度分析:
- 构建前缀和数组:O(n)
- 遍历所有切割点:O(n)
- 总时间复杂度:O(n)
空间复杂度:O(n)(也可以优化到 O(1),只需维护当前总和即可)
3、代码实现:
java :
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
// 读取一行输入并转换为整数数组
String[] numsStr = in.nextLine().split("\\s+");
int[] a = new int[numsStr.length];
for (int i = 0; i < numsStr.length; i++) {
a[i] = Integer.parseInt(numsStr[i]);
}
System.out.println(minCutCost(a));
}
public static int minCutCost(int[] a) {
int n = a.length;
if (n <= 1) return 0;
int[] prefixSum = new int[n + 1];
for (int i = 0; i < n; i++) {
prefixSum[i + 1] = prefixSum[i] + a[i];
}
int minCost = Integer.MAX_VALUE;
for (int p = 0; p < n - 1; p++) {
int sum1 = prefixSum[p + 1];
int sum2 = prefixSum[n] - sum1;
int cost = sum1 * sum2;
if (cost < minCost) {
minCost = cost;
}
}
return minCost;
}
}
示例输入输出
输入:
int[] a = {1, 2, 3, 4, 5};
所有可能的切割及其代价:
切割点 | 左边和 | 右边和 | 代价 |
---|---|---|---|
p=0 | 1 | 14 | 14 |
p=1 | 3 | 12 | 36 |
p=2 | 6 | 9 | 54 |
p=3 | 10 | 5 | 50 |
输出:
14
php :
<?php
function minCutCost($a) {
$n = count($a);
if ($n <= 1) return 0;
// 构建前缀和数组
$prefixSum = array_fill(0, $n + 1, 0);
for ($i = 0; $i < $n; $i++) {
$prefixSum[$i + 1] = $prefixSum[$i] + $a[$i];
}
$minCost = PHP_INT_MAX;
for ($p = 0; $p < $n - 1; $p++) {
$sum1 = $prefixSum[$p + 1];
$sum2 = $prefixSum[$n] - $sum1;
$cost = $sum1 * $sum2;
if ($cost < $minCost) {
$minCost = $cost;
}
}
return $minCost;
}
// 使用 fscanf 读取输入
$handle = fopen("php://stdin", "r");
// 假设第一行输入表示数组长度
$len = 0;
fscanf($handle, "%d", &$len);
// 然后读取 len 个整数
$a = [];
for ($i = 0; $i < $len; $i++) {
$num = 0;
fscanf($handle, "%d", &$num);
$a[] = $num;
}
// 计算并输出结果
echo minCutCost($a) . PHP_EOL;
fclose($handle);
golang :
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func minCutCost(a []int) int {
n := len(a)
if n <= 1 {
return 0
}
prefixSum := make([]int, n+1)
for i := 0; i < n; i++ {
prefixSum[i+1] = prefixSum[i] + a[i]
}
minCost := 1 << 63 // 初始化为最大值
for p := 0; p < n-1; p++ {
sum1 := prefixSum[p+1]
sum2 := prefixSum[n] - sum1
cost := sum1 * sum2
if cost < minCost {
minCost = cost
}
}
return minCost
}
func main() {
// 读取数组长度
var n int
fmt.Print("请输入数组长度 n: ")
_, err := fmt.Scan(&n)
if err != nil || n < 2 {
fmt.Println("输入错误,n 必须大于等于 2")
return
}
// 读取 n 个整数
a := make([]int, n)
fmt.Printf("请输入 %d 个整数(空格分隔):\n", n)
for i := 0; i < n; i++ {
_, err := fmt.Scan(&a[i])
if err != nil {
fmt.Println("输入错误:请输入整数")
return
}
}
// 计算并输出结果
fmt.Println("最小切割代价为:", minCutCost(a))
}
//第二种输入输出实现
/*func main() {
reader := bufio.NewReader(os.Stdin)
line, _ := reader.ReadString('\n')
numsStr := strings.Fields(line)
nums := make([]int, len(numsStr))
for i, s := range numsStr {
nums[i], _ = strconv.Atoi(s)
}
fmt.Println(minCutCost(nums))
}*/