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 将正收益的前缀和计入总分 完美模拟了多次合并操作对总分的贡献,无需真正修改卡牌序列
相关推荐
起予者汝也1 小时前
Python 数据结构
开发语言·数据结构·python
石一峰6992 小时前
驱动:私有数据为什么要在三个地方各挂一遍?
数据库·python·算法
卡提西亚3 小时前
leetcode-179. 最大数
python·算法
浩瀚地学3 小时前
【面试算法笔记】0302-哈希表-哈希表实现
java·经验分享·笔记·算法·面试
ECT-OS-JiuHuaShan3 小时前
严格证明:还原论是ASCII,整体论是UTF-8的历史意义和价值
开发语言·人工智能·算法·量子计算
ShallWeL3 小时前
【机器学习】(19)—— 数据特性与标签
人工智能·算法·机器学习
Frostnova丶3 小时前
(17)LeetCode 41. 缺失的第一个正数
数据结构·算法·leetcode
旖-旎4 小时前
《LeetCode 416 分割等和子集》
c++·算法·leetcode·动态规划·背包问题