P6625 [省选联考 2020 B 卷] 卡牌游戏

记录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代码的堆砌,而是致力于拆解题目背后的逻辑链条与核心知识点
  • 备赛路上若遇瓶颈,欢迎随时评论或私信,我将甄选典型疑难问题,通过视频讲解或撰写专项文章的形式,为你提供深度答疑。

核心解题思路

这道题是一道非常经典的贪心算法与前缀和结合的问题。

  1. 问题转化(前缀和视角)

    题目要求我们每次选取"最左边的连续若干张卡牌"进行合并。这意味着,无论我们怎么操作,本质上都是在不断地选取数组的前缀。

    假设我们第一次合并了前 i 张卡牌,那么这次操作对总分的贡献就是前 i 张卡牌的和(即前缀和 Si​ )。合并后,这 i 张卡牌变成了一张分值为 Si​ 的新卡牌,位于最左端。

    如果我们接下来继续合并前 j 张卡牌(包含刚才合并出的那张新卡牌),这次操作对总分的贡献,本质上就是原数组前 j 张卡牌的和(即前缀和 Sj​ )。

    因此,整个游戏过程的总分,实际上就是我们选取的若干个"前缀和"的累加。

  2. 算法设计(贪心策略)

    既然总分等于我们选出的前缀和之和,为了让总分最大,我们只需要遵循一个简单的贪心原则:只要当前的前缀和是正数,我们就把它合并进来! 因为正数会让总分变大。如果当前的前缀和是负数或零,合并它只会让总分不变或减少,我们就应该立刻停止游戏。

    注意题目限制"至少合并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 将正收益的前缀和计入总分 完美模拟了多次合并操作对总分的贡献,无需真正修改卡牌序列
相关推荐
渡我白衣36 分钟前
深入理解 Transformer:Transformer 究竟是什么?
java·linux·开发语言·c++·人工智能·深度学习·transformer
无限码力1 小时前
8.26华为OD机试真题 新系统【字符串回文判断】
算法·华为od·华为od机考·华为od机试·华为od上机考试真题·华为od最新机试真题题解·华为od机试真题题库
玖玥拾3 小时前
LeetCode 125 验证回文串
算法·leetcode
「QT(C++)开发工程师」9 小时前
C++ 11 常用for循环
开发语言·c++
我还记得那天9 小时前
0 初识C++
开发语言·c++
Asize10 小时前
146. LRU 缓存
算法
Asize10 小时前
543. 二叉树的直径
算法
lemon_sjdk10 小时前
ObjectProperty
java·开发语言·算法
Zentceh10 小时前
AI-ISP在夜视机芯中的应用:从传统ISP到PixelClean全彩夜视的进化
人工智能·科技·算法·计算机视觉·车载系统·视频·智能硬件