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

159 lines
6.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## [$AcWing$ $455$. 小朋友的数字](https://www.acwing.com/problem/content/457/)
### 一、题目描述
有 $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** :
```cpp {.line-numbers}
5 997
1 2 3 4 5
```
**输出样例 1**:
```cpp {.line-numbers}
21
```
**输入样例 2**:
```cpp {.line-numbers}
5 7
-1 -1 -1 -1 -1
```
**输出样例 2**:
```cpp {.line-numbers}
-1
```
### 二、前置知识
**[$AcWing$ $126$. 最大的和](https://www.cnblogs.com/littlehb/p/17744300.html)**
我们用 生活场景类比 ,把这三个概念说得更直白一些:
#### 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 取模输出。
#### 三者的关系
数字 →(计算最大子段和)→ 特征值 →(计算前面分数+特征值的最大值)→ 分数
简单来说:数字是原始数据,特征值是数字的「连续和最大值」,分数是基于前面所有小朋友的「分数+特征值」推导出来的最终结果。
### 三、解题思路
```cpp {.line-numbers}
#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$
```cpp {.line-numbers}
#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;
}
```