当前位置: 首页 > java >正文

每日一题——樱桃分级优化问题:最小化标准差的动态规划与DFS解决方案

文章目录

    • 一、问题描述
      • 输入格式
      • 输出格式
    • 二、问题本质分析
    • 三、解题思路
      • 1. 前缀和预处理
      • 2. DFS 枚举与剪枝
      • 3. 剪枝策略
      • 4. 标准差计算
    • 四、代码实现
    • 五、样例解析
      • 样例 1
      • 样例 2
    • 六、一行行代码带你敲
      • dfs
    • 七、总结

一、问题描述

某大型樱桃加工厂使用自动化机械扫描了一批樱桃的尺寸大小,获得了直径范围 [L, H] 内各个区间的所有樱桃个数统计。现在需要通过 m 个等级(m < H - L)来筛选不同尺寸大小的樱桃,筛选后需使得各等级内的樱桃数量和的标准差最小。

输入格式

  • 第一行:两个整数 n(樱桃总组数,2 < n ≤ 20)和 m(需要的等级数,2 < m < n)。
  • 第二行:长度为 n 的整数序列 A = [a_0, a_1, …, a_{n-1}],其中 a_i 表示第 i 组直径对应的樱桃个数(0 < a_i < 100)。

输出格式

输出长度为 m 的序列 B = [b_0, b_1, …, b_{m-1}],其中:

  • b_0 表示从 A 的第 0 位开始,顺序取 b_0 个元素作为第 1 个等级;
  • b_1 表示从 A 的第 b_0 位开始,顺序取 b_1 个元素作为第 2 个等级;
  • 依次类推,保证所有元素被分配且所得到的等级和序列和的标准差最小。

二、问题本质分析

这是一个将长度为 n 的序列分割成 m 段,使得每一段元素之和的标准差最小的分割优化问题。由于 n ≤ 20,可以使用 动态规划 + 枚举 或者 DFS + 剪枝 来搜索最优分割方案。

三、解题思路

1. 前缀和预处理

预处理序列 A 的前缀和 S,使得任意区间和 sum(i, j) = S[j + 1] - S[i] 能够在 O(1) 时间内获得。

2. DFS 枚举与剪枝

递归枚举每一段的长度,维护以下信息:

  • 已分配的段数。
  • 当前位置。
  • 已经选取的各段和列表。

当分配到第 m 段时,将剩余元素作为最后一段,计算所有段和的标准差,并记录最小标准差的分割方案。

3. 剪枝策略

  • 若当前已分配段数与剩余元素无法满足分段数时,直接剪枝。

整理如下,保持结构清晰,并将公式按 CSDN Markdown 规范用 $$ 包裹:

4. 标准差计算

对于段和数组 W = [w_0, w_1, …, w_{m-1}],其平均值为:

μ = 1 m ∑ k = 0 m − 1 w k \mu = \frac{1}{m} \sum_{k=0}^{m-1} w_k μ=m1k=0m1wk

标准差为:

σ = 1 m ∑ k = 0 m − 1 ( w k − μ ) 2 \sigma = \sqrt{ \frac{1}{m} \sum_{k=0}^{m-1} (w_k - \mu)^2 } σ=m1k=0m1(wkμ)2

在代码中,通过 accumulate 求平均值,再依次计算方差并开方,即可得出标准差,用于评估分段划分的均匀性。标准差越小,说明划分越均匀。需要注意,为避免整数除法造成精度丢失,平均值计算时应以 0.0 作为初始值确保类型为 double

四、代码实现

以下是完整的C++代码实现,包含详细注释:

#include <bits/stdc++.h>
using namespace std;int n, m; // 樱桃总组数和需要的等级数
vector<int> A; // 每组樱桃的数量
vector<int> S; // 前缀和数组double best_std = 1e300; // 当前最小的标准差
vector<int> best_B; // 最优分割方案// 计算区间和 [l, r)
int intervalSum(int l, int r) {return S[r] - S[l];
}// 深度优先搜索 (DFS) 枚举分割方案
void dfs(int pos, int k, vector<int>& W, vector<int>& B) {if (k == m - 1) { // 如果已经分配了 m-1 段int len = n - pos; // 剩余部分作为最后一段B.push_back(len);W.push_back(intervalSum(pos, n)); // 计算最后一段的和// 计算平均值double mu = accumulate(W.begin(), W.end(), 0.0) / m;// 计算方差double var = 0;for (double w : W) var += (w - mu) * (w - mu);var /= m;double std = sqrt(var); // 计算标准差if (std < best_std) { // 如果当前标准差更小best_std = std; // 更新最小标准差best_B = B; // 更新最优分割方案}B.pop_back(); W.pop_back(); // 回溯return;}// 枚举当前段的长度for (int len = 1; pos + len + (m - k ) <= n; ++len) {B.push_back(len); // 将当前段长度加入方案int w = intervalSum(pos, pos + len); // 计算当前段的和W.push_back(w);dfs(pos + len, k + 1, W, B); // 递归枚举下一段B.pop_back(); W.pop_back(); // 回溯}
}int main() {cin >> n >> m; // 输入樱桃总组数和等级数A.resize(n); // 初始化樱桃数量数组for (int i = 0; i < n; ++i) cin >> A[i]; // 输入每组樱桃的数量S.assign(n + 1, 0); // 初始化前缀和数组for (int i = 0; i < n; ++i) S[i + 1] = S[i] + A[i]; // 计算前缀和vector<int> W, B; // 当前段和列表和当前分割方案dfs(0, 1, W, B); // 开始深度优先搜索for (int x : best_B) cout << x << ' '; // 输出最优分割方案return 0;
}

五、样例解析

样例 1

输入

9 3
1 2 3 4 5 6 7 8 9

输出

5 2 2

解析

  • 将 9 组樱桃分为 3 组,使得三组樱桃数量和的标准差最小。
  • 分割方案为:
    • 第一组:1 + 2 + 3 + 4 + 5 = 15
    • 第二组:6 + 7 = 13
    • 第三组:8 + 9 = 17
  • 段和为 [15, 13, 17],平均值为 15,标准差为 sqrt((15-15)^2 + (15-13)^2 + (15-17)^2) / 3,为所有筛选方案中的最小值。

样例 2

输入

10 4
16 40 37 20 18 30 18 60 50 37

输出

3 3 2 2

解析

  • 分割方案为:
    • 第一组:16 + 40 + 37 = 93
    • 第二组:20 + 18 + 30 = 68
    • 第三组:18 + 60 = 78
    • 第四组:50 + 37 = 87
  • 段和为 [93, 68, 78, 87],平均值为 81.5,标准差为 sqrt((93-81.5)^2 + (68-81.5)^2 + (78-81.5)^2 + (87-81.5)^2) / 4,为所有筛选方案中的最小值。

六、一行行代码带你敲

#include<bits/stdc++.h>
using namespace std;

零帧起手。

int n, m; // 樱桃总数和需要的等级数
vector<int> A; // 每组樱桃的数量
vector<int> S; // 前缀和数组

没啥好解释的。提前定义好全局变量

int main()

进入main函数

cin>>n>>m;

结构化输入输出,不解释

    A.resize(n); // 初始化樱桃数量数组for (int i = 0; i < n; ++i) cin >> A[i]; // 输入每组樱桃的数量

确定数量组的大小并且给A[i]赋值

S.assign(n + 1, 0); // 初始化前缀和数组for (int i = 0; i < n; ++i) S[i + 1] = S[i] + A[i]; // 计算前缀和

给前缀和进行赋值。注意提前assign(n + 1, 0);否则S默认值未知

 vector<int> W, B; // 当前段和当前分割方案

dfs

重头戏来了

dfs(0, 0, W, B); // 开始深度优先搜索

第一个参数是开始节点,第二个参数是长度,第三个参数是段和数组,第四个参数是分割方案。表示从0节点开始分割,长度为0,段和数组为空,分割方案为空。

void dfs(int pos, int k, vector<int>& W, vector<int>& B) 

开始分割

for (int len = 1; pos + len + (m - k ) <= n; ++len)

初始长度肯定从1开始,从开始节点开始分割,一定要开始节点+长度+(m-k-1)<=数组长度。m-k-1,表示还剩下的几组,

        B.push_back(len); // 将当前段长度加入方案

将当前段放进方案里面,不处理标准差,等方案确定再一起处理

int w = intervalSum(pos, pos + len); // 计算当前段的和
W.push_back(w);

计算当前段的和,并且push进去

// 计算区间和 [l, r)
int intervalSum(int l, int r) {return S[r] - S[l];
}

intervalSum函数的实现

dfs(pos + len, k + 1, W, B); // 递归枚举下一段

想象着一直递归枚举下去,把m段分好,一直分到最后一段。

    if (k == m ) {return;}

分配到最后一段,也可以return了

   int len = n - pos; // 剩余部分作为最后一段B.push_back(len);W.push_back(intervalSum(pos, n)); // 计算最后一段的和// 计算平均值double mu = accumulate(W.begin(), W.end(), 0.0) / m;

W:是一个 vector,存储了每个段的和。
W.begin():指向 W 的第一个元素的迭代器。
W.end():指向 W 的最后一个元素之后的位置的迭代器。
0.0:累加的初始值,这里使用 0.0 而不是 0,是为了确保计算结果是 double 类型,避免整数除法导致的精度丢失。
/ m:将累加结果除以段的数量 m,得到平均值。

// 计算方差double var = 0;for (double w : W) var += (w - mu) * (w - mu);var /= m;double std = sqrt(var); // 计算标准差

最后两行完全没必要,m是一个输入的定值,var和std是平方关系,没必要开根号。

        if (std < best_std) { // 如果当前标准差更小best_std = std; // 更新最小标准差best_B = B; // 更新最优分割方案}

更新方案

double best_std = 1e300; // 当前最小的标准差
vector<int> best_B; // 最优分割方案

别忘了前面要定义一个全局变量,储存方案

        B.pop_back(); W.pop_back(); // 回溯

七、总结

通过前缀和预处理和深度优先搜索(DFS)枚举所有可能的分割方案,结合剪枝策略,可以高效地找到使得各等级内樱桃数量和的标准差最小的分割方案。这种方法适用于 n 较小的情况(如本题中的 n ≤ 20),能够在合理的时间内找到最优解。

http://www.xdnf.cn/news/5742.html

相关文章:

  • 物理:海市蜃楼是宇宙背景辐射吗?
  • 【速写】use_cache参数与decode再探讨
  • 计算机网络笔记(二十四)——4.6互联网的路由选择协议
  • 基于STM32、HAL库的BMP390L气压传感器 驱动程序设计
  • Costmap代价地图
  • IOT藍牙探測 C2 架構:社會工程/節點分離防追尋
  • 2.1 微积分基本想法
  • ABP-Book Store Application中文讲解 - Part 2: The Book List Page - TBD
  • 案例-流量统计
  • 网格图之bfs
  • 水平翻转 垂直翻转 颜色处理
  • 二、HAL库的命名规则详解
  • 【Python】Python 单例模式 8 大核心应用场景深度解析(2025 新版)
  • 前端vue+elementplus实现上传通用组件
  • 非结构化数据的智能化蜕变:从混沌到知识的进化之路
  • Python教程(四)参数提取pymysql
  • 直方图详解
  • Python | Dashboard制作 【待续】
  • 1.3.3 tinyalsa详细介绍
  • 14.three官方示例+编辑器+AI快速学习webgl_buffergeometry_instancing_interleaved
  • 【语法】C++的多态
  • 专题二:二叉树的深度优先搜索
  • AI+Java开发项目——石头迷阵游戏
  • M0基础篇之DAC
  • LAN-402 全国产信号采集处理模块K7-325T(4通道采集)
  • LC滤波器与电感、电容的区别:技术分析与应用
  • springboot做junit单元测试详细步骤
  • 深入理解 iOS 开发中的 `use_frameworks!`
  • 大数据课设——基于电影数据集,分析导演影响力,绘制各种可视化图表
  • 【Linux】Linux内核的网络协议之socket理解