记录149
cpp
#include<bits/stdc++.h>
using namespace std;
#define ll long long // 数据范围到10^5,累加后可能爆int,必须开long long
int main() {
ios::sync_with_stdio(false); // 加速IO
cin.tie(0);
int n;
cin>>n;
ll sum=0,ans=0,a;
for (int i=1;i<=n;i++) {
cin>>a;
sum+=a; // 实时计算前缀和
// 只有前缀和大于0,且不是第一张牌(至少合并2张)时,才加入总分
if (i!=1&&sum>0) ans+=sum;
}
cout<<ans<<"\n";
return 0;
}
题目传送门
https://www.luogu.com.cn/problem/P6625
前言
我是一名专注信奥赛(CSP-J/S、NOIP)的教练。
- 如果你觉得这篇题解对你有帮助,欢迎点击关注我的CSDN账号,我会持续更新高质量算法解析。
- 我深知算法思维的构建远比单纯通过题目更重要,本系列题解不局限于AC代码的堆砌,而是致力于拆解题目背后的逻辑链条与核心知识点
- 备赛路上若遇瓶颈,欢迎随时评论或私信,我将甄选典型疑难问题,通过视频讲解或撰写专项文章的形式,为你提供深度答疑。
核心解题思路
这道题是一道非常经典的贪心算法与前缀和结合的问题。
-
问题转化(前缀和视角) :
题目要求我们每次选取"最左边的连续若干张卡牌"进行合并。这意味着,无论我们怎么操作,本质上都是在不断地选取数组的前缀。
假设我们第一次合并了前 i 张卡牌,那么这次操作对总分的贡献就是前 i 张卡牌的和(即前缀和 Si )。合并后,这 i 张卡牌变成了一张分值为 Si 的新卡牌,位于最左端。
如果我们接下来继续合并前 j 张卡牌(包含刚才合并出的那张新卡牌),这次操作对总分的贡献,本质上就是原数组前 j 张卡牌的和(即前缀和 Sj )。
因此,整个游戏过程的总分,实际上就是我们选取的若干个"前缀和"的累加。
-
算法设计(贪心策略) :
既然总分等于我们选出的前缀和之和,为了让总分最大,我们只需要遵循一个简单的贪心原则:只要当前的前缀和是正数,我们就把它合并进来! 因为正数会让总分变大。如果当前的前缀和是负数或零,合并它只会让总分不变或减少,我们就应该立刻停止游戏。
注意题目限制"至少合并2张卡牌",所以我们从第2张卡牌开始判断前缀和是否为正。
代码分块详细解释
1. 头文件与变量定义
cpp
#include<bits/stdc++.h>
using namespace std;
#define ll long long // 数据范围到10^5,累加后可能爆int,必须开long long
int main() {
ios::sync_with_stdio(false); // 加速IO
cin.tie(0);
int n;
cin >> n;
ll sum = 0, ans = 0, a;
- 详细分析 :卡牌的分值 aiai 绝对值最大可达 10^5,卡牌数量 nn 最大也是 10^5 。在极端情况下,前缀和的累加可能会达到 10^10 的级别,这远远超过了
int类型的表示范围(约 2×10^9 )。因此,代码中定义了ll(long long) 来存储前缀和sum和最终答案ans,这是防止数据溢出的关键。
2. 核心逻辑:线性扫描与贪心累加
cpp
for (int i = 1; i <= n; i++) {
cin >> a;
sum += a; // 实时计算前缀和
// 只有前缀和大于0,且不是第一张牌(至少合并2张)时,才加入总分
if (i != 1 && sum > 0) ans += sum;
}
cout << ans << "\n";
return 0;
}
- 详细分析 :这是代码的灵魂所在,仅用一个循环就解决了问题。
- 实时前缀和 :在读取每一个卡牌分值
a的同时,立刻将其累加到sum中。此时的sum就代表了原数组从第 1 张到第i张卡牌的总和。 - 贪心判定 :
if (i != 1 && sum > 0)包含两个核心条件。i != 1满足了题目"至少选取2张卡牌"的硬性约束;sum > 0则是贪心策略的体现。只要当前累积的前缀和是正收益,我们就执行这次合并操作,将sum加到总分ans中。 - 为什么不需要复杂的模拟? 因为无论中间合并了多少次,下一次合并最左侧卡牌时,其分值总和永远等于当前遍历到的位置的前缀和。我们只需要一路向右扫描,遇到正的前缀和就"吃掉",遇到负的前缀和就"停手"(后续的负前缀和不会再被加入
ans),这样就能保证得到的ans是全局最大值。
- 实时前缀和 :在读取每一个卡牌分值
核心逻辑总结表
| 代码模块 | 核心变量/操作 | 精炼作用 | 解决的痛点 |
|---|---|---|---|
| 数据类型 | #define ll long long |
使用 64 位整数存储累加和 | 防止在卡牌数量与分值均达到 105105 时,总分计算发生整型溢出 |
| 前缀和计算 | sum += a |
在读取数据的同时计算前缀和 | 将复杂的"卡牌合并与替换"过程,转化为直观的数学前缀和累加 |
| 规则约束 | i != 1 |
限制从第2张卡牌开始判断 | 严格遵守了题目中"每次至少选取2张卡牌"的操作限制 |
| 贪心策略 | sum > 0 |
仅当当前前缀和为正时才累加 | 保证了每一步操作都能使总分增加,从而获得全局最优解 |
| 答案累加 | ans += sum |
将正收益的前缀和计入总分 | 完美模拟了多次合并操作对总分的贡献,无需真正修改卡牌序列 |