题解:洛谷 P1614 爱与愁的心痛
【题目来源】
【题目描述】
最近有 \(n\) 个不爽的事,每句话都有一个正整数刺痛值(心理承受力极差)。爱与愁大神想知道连续 \(m\) 个刺痛值的和的最小值是多少,但是由于业务繁忙,爱与愁大神只好请你编个程序告诉他。
【输入】
第一行有两个用空格隔开的整数,分别代表 \(n\) 和 \(m\)。
第 \(2\) 到第 \((n+1)\) 行,每行一个整数,第 \((i+1)\) 行的整数 \(a_i\) 代表第 \(i\) 件事的刺痛值 \(a_i\)。
【输出】
输出一行一个整数,表示连续 \(m\) 个刺痛值的和的最小值是多少。
【输入样例】
8 3
1
4
7
3
1
2
4
3
【输出样例】
6
【核心思想】
-
问题分析:给定一个长度为 \(n\) 的序列,需要找出所有长度为 \(m\) 的连续子序列中,和的最小值。本质是一个固定长度滑动窗口的最小和问题。由于 \(n\) 和 \(m\) 通常不大(如 \(n \le 3000\)),可以直接枚举所有子序列并求和,也可以使用前缀和或滑动窗口优化。
-
算法选择:
- 双重循环枚举:对于每个起始位置 \(i\)(\(0 \le i \le n-m\)),计算子序列 \(a[i..i+m-1]\) 的和,并更新最小值。时间复杂度 \(O(n \cdot m)\),在 \(n \le 3000\) 时完全可行。
- 前缀和优化(可选):预处理前缀和数组 \(prefix\),则子序列和 \(sum(i, i+m-1) = prefix[i+m] - prefix[i]\),可将时间复杂度降为 \(O(n)\)。但本题数据规模下,双重循环已经足够。
-
关键步骤:
- 读入与存储:读取 \(n\)、\(m\) 和序列 \(a[0..n-1]\)。
- 初始化最小值:将
min_sum设置为一个很大的数(如INT_MAX)。 - 枚举所有长度为 \(m\) 的子序列:
- 外层循环 \(i\) 从 \(0\) 到 \(n-m\),表示子序列的起始位置。
- 内层循环计算 \(a[i]\) 到 \(a[i+m-1]\) 的和。
- 将当前和与
min_sum比较,取较小值。
- 输出:
min_sum。
-
时间/空间复杂度:
- 时间复杂度:\(O(n \cdot m)\),对于 \(n \le 3000\) 可接受。
- 空间复杂度:\(O(n)\),用于存储序列。
-
模拟与窗口求和:
- 固定窗口:本题中 \(m\) 是固定的,因此只需要考虑长度恰好为 \(m\) 的连续子序列。
- 暴力枚举:对于入门题,直接枚举所有可能窗口并求和是最直观的思路,也容易理解和实现。
- 边界处理:注意循环上限为 \(n-m\)(包含),确保窗口不越界。
- 适用场景:这种固定长度窗口的最值问题,是滑动窗口算法的入门基础。当数据规模较小时,暴力枚举是可靠的解法。
【算法标签】
入门 #模拟
【代码详解】
#include <bits/stdc++.h> // 包含标准库头文件(万能头文件)
using namespace std; // 使用标准命名空间
int a[3000]; // 定义全局数组:存储输入的数字序列
int main()
{
int n, m; // 定义变量:n-数字总数,m-子序列长度
int i, j; // 定义循环变量
int sum = 0; // 定义变量:当前子序列的和
int min = 2147483647;// 定义变量:最小子序列和,初始为最大整数值
cin >> n >> m; // 输入数字总数和子序列长度
// 输入数字序列
for (int i = 0; i < n; i++)
{
cin >> a[i];
}
// 特殊情况处理:当子序列长度等于总长度时
if (m == n)
{
// 直接计算整个序列的和
for (i = 0; i < n; i++)
{
sum += a[i];
}
min = sum; // 整个序列的和就是最小值
}
else
{
// 常规情况:遍历所有可能的子序列
for (i = 0; i <= n - m; i++)
{
sum = 0; // 重置当前子序列和
// 计算当前子序列的和
for (j = i; j < i + m; j++)
{
sum += a[j];
}
// 更新最小子序列和
if (sum < min)
min = sum;
}
}
cout << min; // 输出最小子序列和
return 0; // 程序正常结束
}
【运行结果】
8 3
1
4
7
3
1
2
4
3
6
浙公网安备 33010602011771号