Files
python/TangDou/CSP-J/AcWingTraining/T6/455.md
HuangHai 1f397eca87 'commit'
2025-08-30 18:35:01 +08:00

6.3 KiB
Raw Blame History

AcWing 455. 小朋友的数字

一、题目描述

有 n 个小朋友排成一列。

每个小朋友手上都有一个 数字,这个数字可正可负。

注:a[i]

规定每个小朋友的 特征值 等于排在他前面(包括他本人)的小朋友中连续若干个(最少有一个)小朋友手上的数字之和的最大值。

注:$b[i]$=特征值=最大子段和

作为这些小朋友的老师,你需要给每个小朋友一个 分数 ,分数是这样规定的:

注:c[i]

第一个小朋友的分数是他的特征值

注:c[1]=b[1]

其它小朋友的分数为排在他前面的所有小朋友中(不包括他本人),小朋友分数加上其特征值的最大值。 

请计算所有小朋友分数的最大值,输出时保持最大值的符号,将其绝对值对 p 取模后输出。

输入格式 第一行包含两个正整数 $n、p$,之间用一个空格隔开。

第二行包含 n 个数,每两个整数之间用一个空格隔开,表示每个小朋友手上的数字。

输出格式 输出只有一行,包含一个整数,表示最大分数对 $p$取模的结果。

数据范围 1≤n≤10^6,1≤p≤10^9,其他数字的绝对值均不超过10^9

输入样例1 :

5 997
1 2 3 4 5

输出样例 1:

21

输入样例 2:

5 7
-1 -1 -1 -1 -1

输出样例 2:

-1

二、前置知识

AcWing 126. 最大的和

我们用 生活场景类比 ,把这三个概念说得更直白一些:

1. 数字(对应代码里的 x )

  • 直白解释: 每个小朋友本身带有的一个数 (可能是正数、负数或0)。
  • 举例:比如3个小朋友,他们的数字分别是 3 、 -1 、 4 。这就是题目直接给的原始数据。

2. 特征值(对应代码里的 $f[i]$)

  • 直白解释: 前$i$个小朋友的数字中,选一段连续的数字相加,能得到的最大的和 。
  • 关键点:必须是「连续的一段」,不能跳着选;目标是找出这段连续数字的和最大是多少。
  • 举例:
    • 前1个小朋友(数字3):只能选他自己,特征值是 3 。
    • 前2个小朋友(数字3、-1):可以选「3」(和3)或「3+-1=2」,最大的是 3 ,所以特征值是 3 。
    • 前3个小朋友(数字3、-1、4):可以选「3+-1+4=6」或「4」,最大的是 6 ,所以特征值是 6 。
  • 注意:如果前i个数字全是负数,特征值就是最大的那个负数(比如数字是-2、-1,前2个的特征值是-1)。

3. 分数(对应代码里的 res 和 score 维护的值)

  • 直白解释: 每个小朋友的最终得分 ,计算规则分两种情况:
    • 第1个小朋友:分数 = 他的特征值(因为前面没有其他小朋友)。
    • 第i个小朋友(i>1):分数 = 前面所有小朋友中,「分数+特征值」的最大值。
  • 核心逻辑:后面的小朋友可以「继承」前面某个小朋友的分数和特征值之和,取最大的那个作为自己的分数。
  • 举例(接上面的特征值例子):
    • 第1个小朋友分数: 3 (他的特征值)。
    • 第2个小朋友分数:前面只有第1个小朋友,所以分数是 3+3=6 。
    • 第3个小朋友分数:前面有第1和第2个小朋友,计算 3+3=6 和 6+3=9 ,取最大的 9 。
  • 注意:分数可能会非常大,所以代码里用了 __int128 来防止溢出,最后再对 p 取模输出。

三者的关系

数字 →(计算最大子段和)→ 特征值 →(计算前面分数+特征值的最大值)→ 分数

简单来说:数字是原始数据,特征值是数字的「连续和最大值」,分数是基于前面所有小朋友的「分数+特征值」推导出来的最终结果。

三、解题思路

#include <bits/stdc++.h>

using namespace std;
typedef long long LL;
const int N = 1000010;
int n, p; // n个小朋友,模p
LL f[N];  // DP数组,前序最大子段和

int main() {
    cin >> n >> p; // n个小朋友,模p
    LL s = 0, mx = -1e18;
    for (int i = 1; i <= n; i++) {
        int x;
        cin >> x;
        s = max(s, 0ll) + x; // 最大子段和的套路,如果能继承父亲的财产就继承,如果父亲只有债务,就不管
        mx = max(mx, s);     // 到i为止可以获得的最大子段和
        f[i] = mx;           // 保存到dp数组f[N]中
    }

    /*
      ① 每个小朋友的特征值等于排在他前面(包括他本人)的小朋友中连续若干个
      (最少有一个)小朋友手上的数字之和的最大值。
      ② 第一个小朋友的分数是他的特征值,
      其它小朋友的分数为排在他前面的所有小朋友中(不包括他本人),小朋友分数加上其特征值的最大值。 
    */
    LL res = f[1]; // 第一个小朋友的得分目前是大王

    LL score = f[1] * 2; // 第二个小朋友的得分 = 第一个小朋友的得分+第一个小朋友的特征值

    for (int i = 2; i <= n; i++) {   // 从第二个小朋友开始,到最后一个小朋友枚举
        res = max(res, score);       // res:记录最大分数
        if (f[i] > 0) score += f[i]; // 如果最大子段和大于0,可以接在后面
    }
    printf("%lld\n", res % p); // 对p取模输出
    return 0;
}

上面的代码只能通过10/12,不能AC,因为会爆long long,办法就是用\_\_int128

#include <bits/stdc++.h>

using namespace std;
typedef __int128 LL;
const int N = 1000010;
int n, p;
LL f[N];
LL max(LL a, LL b) {
    if (a >= b) return a;
    return b;
}
int main() {
    cin >> n >> p;
    LL s = 0, mx = -1e36;
    for (int i = 1; i <= n; i++) {
        int x;
        cin >> x;
        s = max(s, 0) + x;
        mx = max(mx, s);
        f[i] = mx;
    }
    LL res = f[1], score = f[1] * 2;
    for (int i = 2; i <= n; i++) {
        res = max(res, score);
        if (f[i] > 0) score += f[i];
    }
    printf("%lld\n", res % p);
    return 0;
}