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

洛谷 P1115 最大子段和 题解

洛谷 P1115 最大子段和 题解(线性 DP)

题意

给定长度为 \(n\) 的序列 \(a\),选出其中连续且非空的一段使其和最大。

数据范围与约定

对于 \(100\%\) 的数据,保证 \(1 \le n \le 2 \times 10^5\)\(-10^4 \le a_i \le 10^4\)

题解

思路一:暴力(\(40\) \(\operatorname{pts}\)

显然,暴力的时间复杂度可以达到 \(O(n^3)\),加上前缀和优化,可以优化到 \(O(n^2)\)

CODE

#include<bits/stdc++.h>
using namespace std;
/*====================*/
using lnt = long long;
using pii = pair<int, int>;
using pll = pair<lnt, lnt>;
/*====================*/
#define endl "\n"
/*====================*/
const int INF = 0x3F3F3F3F;
const lnt INFLL = 0x3F3F3F3F3F3F3F3F;
/*====================*/
const lnt MOD = 998244353;
/*====================*/
void FileIn(const char* filename) {freopen(filename, "r", stdin);}
void FileOut(const char* filename) {freopen(filename, "w", stdout);}
/*====================*/
const int N = 2e5 + 10;
/*====================*/
int n, arr[N];
/*====================*/
lnt sum[N];
/*====================*/
void Init()
{cin >> n;for (int i = 1; i <= n; i++){cin >> arr[i];}/*====================*/for (int i = 1; i <= n; i++){sum[i] = sum[i - 1] + arr[i];}
}
/*====================*/
void Solve()
{lnt ans = -INFLL;for (int l = 1; l <= n; l++){for (int r = l; r <= n; r++){ans = max(ans, sum[r] - sum[l - 1]);}}cout << ans << endl;
}
/*====================*/
int main()
{
#ifndef ONLINE_JUDGEFileIn("IN.txt");
#endifios::sync_with_stdio(false);cin.tie(nullptr), cout.tie(nullptr);int T = 1; //cin >> T;while (T--) Init(), Solve();return 0;
}

思路二:DP(\(100 \operatorname{pts}\)

按照右端点分类。考虑 \(f_i\) 表示以 \(a_i\) 为结尾的区间的最大子段和。把上述区间分为两类:第一类是长度等于 \(1\);第二类是长度大于 \(1\),显然长度等于 \(1\) 时,区间和是 \(a_i\);长度大于 \(1\) 时,你可以选择前面的 \(f_{i - 1}\),即前面的 \(a_{i - 1}, a_{i - 2}, \cdots a_1\) 的选择方案;也可以不选前面的,直接选 \(a_i\),两种方案取最大值即可。则状态转移方程为:

\[f_i = \begin{cases}\max(f_{i - 1} + a_i, a_i) & i > 1 \\a_1 & i = 1 \end{cases} \]

#include<bits/stdc++.h>
using namespace std;
/*====================*/
using lnt = long long;
using pii = pair<int, int>;
using pll = pair<lnt, lnt>;
/*====================*/
#define endl "\n"
/*====================*/
const int INF = 0x3F3F3F3F;
const lnt INFLL = 0x3F3F3F3F3F3F3F3F;
/*====================*/
const lnt MOD = 998244353;
/*====================*/
void FileIn(const char* filename) {freopen(filename, "r", stdin);}
void FileOut(const char* filename) {freopen(filename, "w", stdout);}
/*====================*/
const int N = 2e5 + 10;
/*====================*/
int n, arr[N];
/*====================*/
lnt dp[N];
/*====================*/
void Init()
{cin >> n;for (int i = 1; i <= n; i++){cin >> arr[i];}
}
/*====================*/
void Solve()
{dp[1] = arr[1];for (int i = 2; i <= n; i++){dp[i] = max(dp[i - 1] + arr[i], 1LL * arr[i]);}cout << *max_element(dp + 1, dp + n + 1) << endl;
}
/*====================*/
int main()
{
#ifndef ONLINE_JUDGEFileIn("IN.txt");
#endifios::sync_with_stdio(false);cin.tie(nullptr), cout.tie(nullptr);int T = 1; //cin >> T;while (T--) Init(), Solve();return 0;
}

思路三:分治(\(100 \operatorname{pts}\)

令:

  • \(f(l, r)\) 表示序列 \(a\) 中闭区间 \([l, r]\) 最大子段和;
  • \(\operatorname{Sum}(l, r)\) 表示序列 \(a\) 中闭区间 \([l, r]\) 的和,即 \(\sum \limits _{i = l} ^{r} a_i\)
  • \(\operatorname{Pre}(l, r)\) 表示序列 \(a\) 中闭区间 \([l, r]\) 的最大前缀和;
  • \(\operatorname{Suf}(l, r)\) 表示序列 \(a\) 中闭区间 \([l, r]\) 的最大后缀和。

根据分治的思想,将序列 \(a\) 中的 \([l, r]\) 拆分为 \([l, \operatorname{mid}]\)\([\operatorname{mid} + 1, r]\)。那么 \(f(l, r)\) 答案的来源可能为 \(f(l, \operatorname{mid})\)\(f(\operatorname{mid} + 1, r)\)\(\operatorname{Suf}(l, \operatorname{mid}) + \operatorname{Pre}(\operatorname{mid} + 1, r)\)。其实很容易理解:都在左半边、都在有半边、左半边和有半边都有,那就是左边的一段后缀和 \(+\) 右边的一段前缀和,取最大值即可。

下面给出 \(f(), \operatorname{Sum}(), \operatorname{Pre}(), \operatorname{Suf}()\) 的递推式和解释。

\[f(l, r) = \max(f(l, \operatorname{mid}), f(\operatorname{mid} + 1, r),\operatorname{Suf}(l, \operatorname{mid}) + \operatorname{Pre}(\operatorname{mid} + 1, r));\\\operatorname{Sum}(l, r) = \operatorname{Sum}(l, \operatorname{mid}) + \operatorname{Sum}(\operatorname{mid} + 1, r);\\\operatorname{Pre}(l, r) = \max(\operatorname{Pre}(l, \operatorname{mid}), \operatorname{Sum}(l, \operatorname{mid}) + \operatorname{Pre}(\operatorname{mid} + 1, r));\\\operatorname{Suf}(l, r) = \max(\operatorname{Suf}(\operatorname{mid} + 1, r), \operatorname{Suf}(l, \operatorname{mid}) + \operatorname{Sum}(\operatorname{mid} + 1, r))。 \]

  • 对于 \(f()\),有这么几种可能:都在左半边、都在有半边、左半边和有半边都有,那就是左边的一段后缀和 \(+\) 右边的一段前缀和,取最大值即可;
  • 对于 \(\operatorname{Sum}()\),即为左半边的和 \(+\) 右半边的和;
  • 对于 \(\operatorname{Pre}()\),有这么几种可能:左半边的前缀和、左半边的全部 \(+\) 右半边的前缀和,取最大值;
  • 对于 \(\operatorname{Suf}()\),有这么几种可能:右半边的后缀和、右半边的全部 \(+\) 左半边的后缀和。

既然得出来了这些信息,代码就可以很轻松地写出来啦。

CODE

#include<bits/stdc++.h>
using namespace std;
/*====================*/
using lnt = long long;
using pii = pair<int, int>;
using pll = pair<lnt, lnt>;
/*====================*/
#define endl "\n"
/*====================*/
const int INF = 0x3F3F3F3F;
const lnt INFLL = 0x3F3F3F3F3F3F3F3F;
/*====================*/
const lnt MOD = 998244353;
/*====================*/
void FileIn(const char* filename) {freopen(filename, "r", stdin);}
void FileOut(const char* filename) {freopen(filename, "w", stdout);}
/*====================*/
struct Info
{int sum;int pre;int suf;int val;/*====================*/Info(int _sum = 0, int _pre = 0, int _suf = 0, int _val = 0){sum = _sum, pre = _pre, suf = _suf, val = _val;}/*====================*/friend Info operator+(const Info& l, const Info& r){Info res;res.sum = l.sum + r.sum;res.pre = max(l.pre, l.sum + r.pre);res.suf = max(r.suf, r.sum + l.suf);res.val = max( {l.val, r.val, l.suf + r.pre} );return res;}
};
/*====================*/
const int N = 2e5 + 10;
/*====================*/
int n, arr[N];
/*====================*/
Info Build(int l, int r)
{if (l == r){return Info(arr[l], arr[l], arr[l], arr[l]);}int mid = (l + r) >> 1;return Build(l, mid + 0) + Build(mid + 1, r); 
}
/*====================*/
void Init()
{cin >> n;for (int i = 1; i <= n; i++){cin >> arr[i];}
}
/*====================*/
void Solve()
{cout << Build(1, n).val << endl;
}
/*====================*/
int main()
{
#ifndef ONLINE_JUDGEFileIn("IN.txt");
#endifios::sync_with_stdio(false);cin.tie(nullptr), cout.tie(nullptr);int T = 1; //cin >> T;while (T--) Init(), Solve();return 0;
}

后记

那么,这三种算法到底用了多久呢?我对每个做法都测试了 \(5\) 次(保证不浪费评测机资源的情况下),使用 C++14(GCC 9)O2 优化,取平均值,测试结果如下:

  • 分治:\((60 + 60 + 60 + 60 + 60) \div 5 = 60 \operatorname{ms}\)
  • DP:\((60 + 60 + 60 + 59 + 57) \div 5 = 59.2 \operatorname{ms}\)
  • 暴力:\((3.61 + 3.61 + 3.61 + 3.61 + 3.62) \div 5 = 3.612 \operatorname{ms}\)

其中,洛谷在 TLE 超过 \(1.2 \operatorname{s}\) 时会被截断,所以数据可能不准确,但也足以看出 DP 和分治的高效性了。

2026-02-07 By yufh。

http://www.jsqmd.com/news/355632/

相关文章:

  • 电子学会青少年机器人技术(二级)等级考试试卷-实际操作(2025年12月)
  • 开题报告不用愁!虎贲等考 AI 一键搭框架,让研究思路秒清晰
  • 宏智树 AI:论文双检时代,教你降重降 AIGC 的底层逻辑
  • 电子学会青少年机器人技术(一级)等级考试试卷-实际操作(2025年12月)
  • 电子学会青少年机器人技术(三级)等级考试试卷-实际操作(2025年12月)
  • 【石墨烯】石墨烯载流子密度模型(三维半导体载流子模型拟合到石墨烯模型上)【含Matlab源码 15070期】
  • MySQL 核心数据类型详解与实战案例 - 详解
  • Unity物理引擎:刚体碰撞与力的终极指南
  • 【太阳】Parker太阳风解模型(含物理单位换算、密度剖面及与经验日冕模型的比较)【含Matlab源码 15068期】
  • YOLO多模态融合检测,轻松上手跑自己的数据集实验教学!获取YOLO多模态项目源码,配置虚拟环境,准备数据集、训练、验证、推理测试 。实现0到1的完整教学过程。快速入门必看
  • 《YOLO多模态创新改进专栏目录 》全网独家创新,多模态融合改进教程,包含早期融合、中期融合、后期融合、损失函数改进、二次创新模块、独家创新等几百种创新点改进,答疑群提供完整项目,永久更新中
  • 2026年24小时开锁修锁换锁服务推荐评测:应对紧急突发与价格疑虑的深度排名分析 - 品牌推荐
  • 技术博谈:解析VK视频下载器的实现原理与合理使用
  • 小论文/大论文必备 | YOLO多模态目标检测,计算FPS模型性能 | 测试最优模型FPS指标,既可以凑实验章节工作量、又能助力论文模型性能加分。FPS值越大越好
  • AI写论文哪个软件最好?答案藏在3个学术写作底层需求里
  • 2026年 上海保洁服务公司推荐榜单:专业外墙清洗、门头清洗、地毯清洗、大理石翻新保养与涂料工程一站式服务精选 - 品牌企业推荐师(官方)
  • 小论文/大论文必备 | YOLO多模态目标检测、绘制曲线对比图 | 引入多种绘制曲线对比图,包括mAP0.5,mAP0.5:0.95,Loss损失变化的曲线对比
  • 深度解析与进阶指南:武汉德宝装备机器人工程师职位探微
  • 小论文/大论文必备| YOLO多模态热力图可视化| 引入多种热力图可视化GradCAMPlusPlus, GradCAM, XGradCAM, EigenCAM, HiResCAM等方法,助力论文加分
  • VK视频下载器的技术实现解析与合规应用实践
  • 迈向工业级鲁棒性:深入解析机器人SLAM工程师的核心能力与技术挑战
  • 在线 AI 视频生成最强工具:把灵感直接变成“可用成片”
  • 2026国内最新天然野生沉香厂商top10推荐!广东广州等地优质天然野生沉香公司权威榜单发布,品质服务双优助力沉香收藏与品鉴 - 品牌推荐2026
  • VK视频下载的技术实践:解析公开视频直链的原理与实现
  • 梁实秋《送行》
  • AI元人文:悟空悖论——悬鉴而行
  • hello_agent第十章总结
  • 对tomcat的提供的功能与底层拓扑结构与实现机制的理解
  • 序列化和反序列化
  • 2026国内最新沉香手串供应链top10推荐!广东广州等地优质沉香手串厂家权威榜单发布,品质正宗助力香道品鉴与收藏 - 品牌推荐2026