[NOIP2013-普及]小朋友的数字

GitHub跳转原题关系图返回列表

前缀最大子段和(Kadane 滚动值)+ 分数的前缀最大值 g,一遍 O(n) 扫描求所有人的分数最大值,按符号取模输出。

OJ: roj

题目 ID: 3606

难度:普及-

标签:最大子段和Kadane递推前缀最大值python

创建: 2026-10-02 10:18

更新: 2026-10-06 15:07

题目描述

有 nn 个小朋友排成一列,每人手上有整数 aia_i(可正可负)。特征值 did_i 为前 ii 人中连续非空子段和的最大值;分数 s1=d1s_1=d_1,si (i≥2)s_i\ (i\ge2) 为前面所有人中 sj+djs_j+d_j 的最大值。求 max⁡si\max s_i,保持符号并将绝对值对 pp 取模后输出。

数据范围:1≤n≤1061\le n\le10^6,1≤p≤1091\le p\le10^9,∣ai∣≤109|a_i|\le10^9。

思路

特征值是前缀最大子段和,用 Kadane 滚动 end_here 并维护历史最大 trait。分数需要前面所有人 score+trait 的最大值,再用一个滚动变量 g 维护。全程四个标量,O(n)O(n) 时间、O(1)O(1) 额外空间。注意负数输出保持符号。

参考代码

Python 版:

python
#!/usr/bin/env python3
# Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
# rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
# rainboy的学习导航网站: https://idx.roj.ac.cn
# create_at: 2026-10-02 10:18
# update_at: 2026-10-02 10:18

import sys

NEG = -(1 << 62)  # 分数下界的哨兵:|分数| 不可能超过 n·2·10^9


def solve() -> None:
    data = iter(sys.stdin.buffer.read().split())
    n = int(next(data))
    p = int(next(data))

    best = NEG        # 全体小朋友分数的最大值(带符号)
    end_here = 0      # 以当前人结尾的最大子段和(Kadane 滚动值)
    trait = NEG       # 特征值:前缀 [1..i] 内的最大子段和
    g = NEG           # 已看过的人中 score_j + trait_j 的最大值

    for i in range(n):
        a = int(next(data))
        end_here = max(end_here + a, a)
        trait = max(trait, end_here)  # 特征值不要求子段以 i 结尾

        # 分数:第 1 个人是自己的特征值;
        # 其他人是前面所有人中「分数 + 特征值」的最大值
        score = trait if i == 0 else g
        best = max(best, score)
        g = max(g, score + trait)

    # 输出保持符号:负数输出 -(|best| mod p),非负输出 best mod p
    print(best % p if best >= 0 else -(abs(best) % p))


if __name__ == "__main__":
    solve()

C++ 版(同一算法):

cpp
/**
 * Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
 * rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
 * rainboy的学习导航网站: https://idx.roj.ac.cn
 * create_at: 2026-10-06 15:06
 * update_at: 2026-10-06 15:06
 */

#include <iostream>
#include <algorithm>
using namespace std;

typedef long long ll;

const ll NEG = -(1LL << 62); // 足够小的哨兵

ll n, p;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    cin >> n >> p;

    ll best = NEG;      // 所有分数的最大值(带符号)
    ll end_here = 0;    // 以当前位置结尾的最大子段和
    ll trait = NEG;     // 特征值:前缀 [1..i] 的最大子段和
    ll g = NEG;         // 已处理的人中 score_j + trait_j 的最大值

    for (ll i = 0; i < n; ++i) {
        ll a;
        cin >> a;
        end_here = max(end_here + a, a);
        trait = max(trait, end_here); // 特征值不要求子段以 i 结尾

        ll score = trait;
        if (i > 0) score = g; // 第 1 个人特征值,其余取前面 score+trait 的最大值
        best = max(best, score);
        g = max(g, score + trait);
    }

    if (best >= 0)
        cout << (best % p) << "\n";
    else
        cout << -(abs(best) % p) << "\n";

    return 0;
}