news 2026/2/10 6:25:01

【 例 1】石子合并(信息学奥赛一本通- P1569)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【 例 1】石子合并(信息学奥赛一本通- P1569)

【题目描述】

将 n 堆石子绕圆形操场排放,现要将石子有序地合并成一堆。规定每次只能选相邻的两堆合并成新的一堆,并将新的一堆的石子数记做该次合并的得分。

请编写一个程序,读入堆数 n 及每堆的石子数,并进行如下计算:

1、选择一种合并石子的方案,使得做 n−1 次合并得分总和最大。

2、选择一种合并石子的方案,使得做 n−1 次合并得分总和最小。

【输入】

输入第一行一个整数 n,表示有 n 堆石子。

第二行 n 个整数,表示每堆石子的数量。

【输出】

输出共两行:

第一行为合并得分总和最小值,

第二行为合并得分总和最大值。

【输入样例】

4 4 5 9 4

【输出样例】

43 54

【提示】

数据范围与提示:

对于 100% 的数据,有 1≤n≤200。

在动态规划的学习路径上,“石子合并”是一道绕不开的经典题。

如果是“一排”石子,相信大家闭着眼都能写出状态转移方程。但如果题目稍微变一下,把石子摆成“一圈”(圆形操场),很多同学就慌了:首尾相接了,这区间怎么定?数组怎么开?

今天我们就来彻底解决这个问题,掌握解决环形 DP 问题的核心技巧——断环成链

1. 问题背景

题目描述

有N堆石子摆成一个圆环,每次只能合并相邻的两堆,合并的得分是两堆石子数量之和。

求:

  1. 将这N堆石子合并成一堆的最小得分。

  2. 将这N堆石子合并成一堆的最大得分。

数据范围


2. 算法分析:断环成链

核心痛点

普通的区间DP依赖于线性的数组下标[1, N]。但在环形结构中,第N堆和第1堆也是相邻的,如果我们强行在圆上做DP,下标处理会非常麻烦(涉及取模运算),而且难以枚举所有可能的“切断点”。

解决方案

我们可以使用“断环成链”的技巧:

  1. 复制数组:将长度为N的原数组A,复制一份接在末尾,变成长度为2N的新数组。

  2. 化圆为线:在这个长为2N的数组上,任意截取一段长度为N的子数组,都对应原圆环的一种“断开”方式。

图解示例

假设N=3,石子为[4, 5, 9]

复制后变成:[4, 5, 9, 4, 5, 9]

  • 区间[1, 3](4,5,9) 对应原环(以 9-4 之间断开)。

  • 区间[2, 4](5,9,4) 对应原环(以 4-5 之间断开)。

  • 区间[3, 5](9,4,5) 对应原环(以 5-9 之间断开)。

这样,我们就把一个复杂的环形问题,转化为了在2N长度的数组上求解长度为N的区间 DP 问题

状态定义

我们定义两个数组:

  • dp1[i][j]:合并第i到第j堆石子的最小代价。

  • dp2[i][j]:合并第i到第j堆石子的最大代价。

状态转移方程

这与普通石子合并完全一致:

枚举分割点k ():

dp1[i][j] =

dp2[i][j] =

其中使用前缀和S在O(1)时间内求出:S[j] - S[i-1]。


3. 完整代码

//圆形排放,就把n堆石子复制一遍加到末尾,就可以化环为链去解决 #include <iostream> #include <algorithm>//对应min max #include <cstring>//对应memset using namespace std; int a[420];//存每堆石子多少个 数组开2倍大小 int dp1[420][420];//dp1[i][j]代表合并i到j的最小得分总和 int dp2[420][420];//dp2[i][j]代表合并i到j的最大得分总和 int s[420];//求前缀和 int main(){ //求最小得分总和就初始化dp1为无穷大 memset(dp1,0x3f,sizeof(dp1)); //求最大得分总和就初始化dp2为0 memset(dp2,0,sizeof(dp2)); int n; cin>>n; for(int i=1;i<=n;i++){ cin>>a[i]; //圆形排放,就把n堆石子复制一遍加到末尾,就可以化环为链去解决 a[i+n]=a[i]; } n=n*2;//链长度 //自己合并自己得分为0 for(int i=1;i<=n;i++){ dp1[i][i]=0; dp2[i][i]=0; } //求前缀和 for(int i=1;i<=n;i++) s[i]=s[i-1]+a[i]; //从小到大枚举区间长度 大区间总是由小区间构成 for(int len=2;len<=n/2;len++){ //枚举左端点位置 for(int i=1;i<=n-len+1;i++){ int j=i+len-1;//右端点 for(int k=i;k<j;k++){//枚举分割点 //自身得分与产生左区间得分+产生右区间得分+左右区间合并得分比大小 //s[j]-s[i-1]是本次合并的得分 dp1[i][j]=min(dp1[i][j],dp1[i][k]+dp1[k+1][j]+s[j]-s[i-1]); dp2[i][j]=max(dp2[i][j],dp2[i][k]+dp2[k+1][j]+s[j]-s[i-1]); } } } //统计答案,答案不一定是dp[1][N],而是所有长度为N的区间中的最优值 int ma=0; int mi=0x3f3f3f3f; for(int i=1;i<=n/2;i++){ mi=min(mi,dp1[i][i+n/2-1]); ma=max(ma,dp2[i][i+n/2-1]); } cout<<mi<<endl<<ma; return 0; }

4. 易错点与总结

  1. 数组大小要翻倍

    因为我们把数组复制了一遍,长度变成了2N,所以数组大小必须开到400以上(题目)。如果只开200会越界。

  2. 循环边界优化

    第一层循环枚举len时,虽然链的总长是2N,但我们最终只需要长度为N的结果。所以len循环到n(即N) 就可以停止了,这样可以节省一半的计算时间。

  3. 答案统计方式

    普通石子合并的答案是dp[1][n]

    但环形问题的答案,是需要在2N的链上,遍历所有长度为N的区间,取其中的最大值/最小值。即遍历i从1到N,取dp[i][i+N-1]

  4. 初始化细节

    minmemset0x3f,求max时用0。且dp[i][i](自己合并自己)代价必须显式设为 0,否则无穷大会导致结果错误。

总结

以后遇到“环形”问题(如环形强盗抢劫、环形最大子数组和),第一时间想到“复制数组、断环成链”,这个技巧是通用的。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/2/4 22:32:58

【区间DP】括号序列:如何求解最长合法子序列?(POJ 2955)

在区间动态规划的题库中&#xff0c;“括号匹配”类问题占据了半壁江山。 很多同学分不清“最长合法子串”和“最长合法子序列”的区别&#xff1a; 子串 (Substring)&#xff1a;必须连续。 子序列 (Subsequence)&#xff1a;可以不连续&#xff0c;中间可以跳过某些字符。 …

作者头像 李华
网站建设 2026/2/6 9:58:53

智能论文辅助工具凭借改写功能和团队协作优势,成为高效学术研究的6款推荐工具之一

当前学术写作领域涌现出多款集成文本生成与查重降重功能的智能辅助工具&#xff0c;这些工具基于前沿的自然语言处理技术&#xff0c;能够协助完成论文框架构建、语言优化及原创度检测等任务&#xff0c;为学位论文和学术报告的撰写提供高效支持。需要明确的是&#xff0c;此类…

作者头像 李华
网站建设 2026/2/6 7:45:24

用点积表示“夹角”

推导 1&#xff1a;用余弦定理&#xff08;最经典&#xff09;在平面或三维里&#xff0c;把向量 a,b 都从原点出发&#xff0c;考虑三角形的三条边&#xff1a;一条边长度&#xff1a;∥a∥另一条边长度&#xff1a;∥b∥第三条边是 a−b长度&#xff1a;∥a−b∥夹角就是 a 与…

作者头像 李华
网站建设 2026/2/8 10:28:38

AI原生应用开发:如何利用自然语言处理提升用户体验?

AI原生应用开发&#xff1a;如何利用自然语言处理提升用户体验&#xff1f; 关键词&#xff1a;AI原生应用、自然语言处理&#xff08;NLP&#xff09;、用户体验&#xff08;UX&#xff09;、意图识别、情感分析、对话系统、多模态交互 摘要&#xff1a;在AI技术爆发的今天&am…

作者头像 李华