云计算百科
云计算领域专业知识百科平台

2026-10-25:统计有效序列数目。用go语言,给定两个正整数 n 和 k。现在要统计满足下面条件的有序序列数量:序列一共包含 k 个正整数,这些数的总和恰好为 n,并且这些数的乘积必须是偶数。序

2026-10-25:统计有效序列数目。用go语言,给定两个正整数 n 和 k。现在要统计满足下面条件的有序序列数量:序列一共包含 k 个正整数,这些数的总和恰好为 n,并且这些数的乘积必须是偶数。序列按位置区分,只要某个位置上的数不同,就算作不同序列。最终结果需要对 1000000007 取余后返回。

1 <= n <= 500000。

1 <= k <= n。

输入: n = 5, k = 3。

输出: 3。

解释:

长度为 k = 3 且和为 5 的序列有:

序列乘积奇偶性
[1, 1, 3] 1 * 1 * 3 = 3 奇数
[1, 2, 2] 1 * 2 * 2 = 4 偶数
[2, 1, 2] 2 * 1 * 2 = 4 偶数
[2, 2, 1] 2 * 2 * 1 = 4 偶数
[1, 3, 1] 1 * 3 * 1 = 3 奇数
[3, 1, 1] 3 * 1 * 1 = 3 奇数

有 3 个序列的乘积是偶数,因此答案是 3。

题目来自力扣4002。

大体步骤如下:

步骤 1:把题目翻译成组合计数

  • 序列有序、有 k 个位置、每个位置是 ≥1 的整数、总和恰好为 n —— 这在组合数学里就是 n 的 k 部分有序分拆(composition)。
  • 位置区分意味着 [1,2,2]、[2,1,2]、[2,2,1] 是三个不同序列,可以直接用「把 n 个 1 排成一排、在 n−1 个缝隙里插 k−1 个隔板」来数,天然带顺序,不需要额外乘排列数。

步骤 2:正难则反 —— 用补集

「乘积是偶数」等价于「至少有一个数是偶数」。直接数「至少一个」要分 1 个偶数、2 个偶数……会陷入容斥,很麻烦。

反过来想:

乘积是偶数 = 总数 − 乘积是奇数

而「乘积是奇数」的充要条件是「k 个数全部是奇数」——这个条件非常强、非常好数。于是问题被简化为两个纯组合数相减。

步骤 3:数总数(隔板法 / 星与棒)

把 n 写成 n 个 1:

1 1 1 … 1 (n 个)

中间有 n−1 个空隙,选 k−1 个空隙插板,就把 n 切成 k 段,每段至少 1 个 1,段长就是序列的每一项。

总数=(n−1k−1)\\text{总数} = \\binom{n-1}{k-1}总数=(k−1n−1​)

代码里就是 comb(n-1, k-1)。

步骤 4:数「全是奇数」的序列(换元 + 可行性判断)

设第 i 项为 ai=2bi+1a_i = 2b_i + 1ai​=2bi​+1,其中 bi≥0b_i \\ge 0bi​≥0(这样 aia_iai​ 自动是正奇数)。

求和:

∑ai=2∑bi+k=n⟹∑i=1kbi=n−k2\\sum a_i = 2\\sum b_i + k = n \\quad\\Longrightarrow\\quad \\sum_{i=1}^{k} b_i = \\frac{n-k}{2}∑ai​=2∑bi​+k=n⟹i=1∑k​bi​=2n−k​

这里出现第一个关键判定:

  • n−k2\\frac{n-k}{2}2n−k​ 必须是非负整数。
  • 非负性:题目给了 k≤nk \\le nk≤n,所以 n−k≥0n-k\\ge0n−k≥0 恒成立,不用管。
  • 整数性:需要 n−kn-kn−k 为偶数。由于 n−kn-kn−k 与 n+kn+kn+k 奇偶性相同,代码里写成 (n+k)%2 == 0。
  • 如果不满足,说明根本凑不出「全奇数」序列,减法项为 0,答案就是总数。

满足时,问题变成「k 个非负整数 bib_ibi​ 之和为 m=n−k2m=\\frac{n-k}{2}m=2n−k​」的经典球盒问题,隔板法(允许空盒)给出:

(m+k−1k−1)=(n−k2+k−1k−1)=(n+k2−1k−1)\\binom{m+k-1}{k-1} = \\binom{\\frac{n-k}{2}+k-1}{k-1} = \\binom{\\frac{n+k}{2}-1}{k-1}(k−1m+k−1​)=(k−12n−k​+k−1​)=(k−12n+k​−1​)

代码里正是 comb((n+k)/2-1, k-1)。注意 Go 里整数除法天然向下取整,而这个分支只在 n+k 为偶数时才进入,所以不会取错。

步骤 5:合起来得到闭式解

ans=(n−1k−1)  −  [(n+k) 为偶数]⋅(n+k2−1k−1)\\text{ans} = \\binom{n-1}{k-1} \\;-\\; \\big[(n+k)\\text{ 为偶数}\\big]\\cdot \\binom{\\frac{n+k}{2}-1}{k-1}ans=(k−1n−1​)−[(n+k) 为偶数]⋅(k−12n+k​−1​)

代码里那一句 + mod 是防负数:两个组合数各自都在 [0,mod)[0, \\text{mod})[0,mod) 内,相减可能落到负数区间,而 Go 的 % 对负数会返回负余数,所以先加一个 mod 再取模,保证结果落在 [0,mod)[0, \\text{mod})[0,mod)。

(可以验证减法项永远不超过总数:全奇数序列是总数的一个子集,所以差值必然 ≥ 0,加一次 mod 足够。)

步骤 6:模意义下如何 O(1) 算组合数

模数 109+710^9+7109+7 是质数,且远大于题目中可能出现的最大下标(n−1 ≤ 499999),因此:

  • 所有 1,2,…,4999991,2,\\dots,4999991,2,…,499999 都与 mod 互质,阶乘取模后非零、可逆;
  • 可以用费马小定理求逆:x−1≡xmod−2(modmod)x^{-1} \\equiv x^{\\text{mod}-2} \\pmod{\\text{mod}}x−1≡xmod−2(modmod)。

代码在 init() 里做了两件事:

  • 正向递推阶乘:fac[i] = fac[i-1] * i % mod,得到 fac[i] = i!。
  • 反向递推阶乘逆元:先花一次快速幂算出 invF[mx-1] = (499999!)^{-1},再从后往前用
    1(i−1)!=ii!=invF[i]⋅i\\frac{1}{(i-1)!} = \\frac{i}{i!} = \\text{invF}[i]\\cdot i(i−1)!1​=i!i​=invF[i]⋅i
    递推出所有 invF[i]。
  • 这个「一次快速幂 + 线性回填」的技巧,把 k 次(甚至 5×10⁵ 次)快速幂压成了 1 次,是这里的核心优化点。

    步骤 7:comb 就是查表

    (nm)=n!m! (n−m)!=fac[n]⋅invF[m]⋅invF[n−m] mod mod\\binom{n}{m} = \\frac{n!}{m!\\,(n-m)!} = \\text{fac}[n]\\cdot \\text{invF}[m]\\cdot \\text{invF}[n-m] \\bmod \\text{mod}(mn​)=m!(n−m)!n!​=fac[n]⋅invF[m]⋅invF[n−m]modmod

    三次数组访问 + 两次乘法取模,O(1)。题目约束下所有下标都合法:

    • n−1≤499999=mx−1n-1 \\le 499999 = \\text{mx}-1n−1≤499999=mx−1;
    • n+k2−1≤n−1\\frac{n+k}{2}-1 \\le n-12n+k​−1≤n−1(因为 k≤nk\\le nk≤n),也在表内;
    • 且 n+k2−1≥k−1\\frac{n+k}{2}-1 \\ge k-12n+k​−1≥k−1(因为 n≥kn\\ge kn≥k),不会出现上标小于下标。

    步骤 8:代入样例 n=5, k=3 走一遍

    • 总数:(42)=6\\binom{4}{2} = 6(24​)=6,对应你表格里那 6 行。
    • n+k = 8 为偶数,需要减全奇数的情况:n−k2=1\\frac{n-k}{2} = 12n−k​=1,即 b1+b2+b3=1b_1+b_2+b_3=1b1​+b2​+b3​=1,解数 (1+3−13−1)=(32)=3\\binom{1+3-1}{3-1}=\\binom{3}{2}=3(3−11+3−1​)=(23​)=3,正好对应 [1,1,3]、[1,3,1]、[3,1,1] 这 3 行。
    • 答案:6 − 3 = 3,与题目输出一致。

    步骤 9:两个边界自检(帮助确认公式没错)

    • k = 1:序列只有 [n],乘积为偶数当且仅当 n 为偶数。公式给出 (00)=1\\binom{0}{0}=1(00​)=1,n 为奇数时减 (⋅0)=1\\binom{\\cdot}{0}=1(0⋅​)=1 得 0,n 为偶数时不减得 1。✔
    • k = n:每项只能是 1,乘积恒为 1(奇数)。公式给出 1−(n−1n−1)=01 – \\binom{n-1}{n-1} = 01−(n−1n−1​)=0。✔

    复杂度

    项目结论说明
    预处理时间 O(mx+log⁡mod)\\mathcal{O}(\\text{mx} + \\log \\text{mod})O(mx+logmod) 阶乘正向递推 5×10⁵ 次;阶乘逆元反向递推 5×10⁵ 次;外加一次快速幂,指数为常数 109+510^9+5109+5,约 30 次乘方取模(即 O(log⁡mod)\\mathcal{O}(\\log \\text{mod})O(logmod),可视为常数)
    单次查询时间 O(1)\\mathcal{O}(1)O(1) 判断奇偶 + 两次 comb(各 3 次数组访问、2 次乘法取模)
    总时间复杂度 O(mx)=O(5×105)\\mathcal{O}(\\text{mx}) = \\mathcal{O}(5\\times10^{5})O(mx)=O(5×105),更严谨写为 O(mx+log⁡mod)\\mathcal{O}(\\text{mx}+\\log\\text{mod})O(mx+logmod) 与 n、k 无关,是常数级预处理 + 常数级查询
    总额外空间复杂度 O(mx)\\mathcal{O}(\\text{mx})O(mx) 两个长度 5×10⁵ 的全局数组 fac 与 invF。Go 在 64 位平台上 int 为 8 字节,合计 2×5×105×8 B≈8 MB2\\times 5\\times10^{5}\\times 8\\ \\text{B} \\approx 8\\ \\text{MB}2×5×105×8 B≈8 MB(约 7.63 MiB),其余只有几个标量变量,为 O(1)\\mathcal{O}(1)O(1)

    值得一提的几个点

    • 空间换时间:如果每次查询都现算阶乘,是 O(n)\\mathcal{O}(n)O(n) 一次;预处理后变成 O(1)\\mathcal{O}(1)O(1) 一次,代价是约 8 MB 常驻内存。对 n 上限 5×10⁵ 来说这个交换非常划算。
    • mx 的取值刚好卡满:mx = 500000 时最大可用下标是 499999,正好等于 n−1 的上限;如果 n 再大一点就会越界,所以数组大小和题目约束是绑定的。
    • 费马逆元成立的前提是 mod 为质数且大于所有参与运算的整数,1e9+7 与 5×10⁵ 的组合完全满足;若换成非质数模或与 n 同量级的小模数,这套阶乘逆元就会失效(fac 可能变成 0 而不可逆)。

    Go完整代码如下:

    package main

    import (
    "fmt"
    )

    const mod = 1_000_000_007
    const mx = 500_000

    var fac [mx]int // fac[i] = i!
    var invF [mx]int // invF[i] = i!^-1 = pow(i!, mod-2)

    func init() {
    fac[0] = 1
    for i := 1; i < mx; i++ {
    fac[i] = fac[i–1] * i % mod
    }

    invF[mx–1] = pow(fac[mx–1], mod–2)
    for i := mx – 1; i > 0; i— {
    invF[i–1] = invF[i] * i % mod
    }
    }

    func pow(x, n int) int {
    res := 1
    for ; n > 0; n /= 2 {
    if n%2 > 0 {
    res = res * x % mod
    }
    x = x * x % mod
    }
    return res
    }

    // 从 n 个数中选 m 个数的方案数
    func comb(n, m int) int {
    return fac[n] * invF[m] % mod * invF[n–m] % mod
    }

    func countValidSequences(n, k int) int {
    ans := comb(n–1, k–1)
    if (n+k)%2 == 0 {
    ans = (ans – comb((n+k)/2–1, k–1) + mod) % mod // +mod 保证答案非负
    }
    return ans
    }

    func main() {
    n := 5
    k := 3
    result := countValidSequences(n, k)
    fmt.Println(result)
    }

    在这里插入图片描述

    Python完整代码如下:

    # -*-coding:utf-8-*-

    MOD = 1_000_000_007
    MX = 500_000

    fac = [1] * MX # fac[i] = i!
    invF = [1] * MX # invF[i] = i!^(-1) = pow(i!, MOD-2)

    def init():
    fac[0] = 1
    for i in range(1, MX):
    fac[i] = fac[i – 1] * i % MOD

    invF[MX – 1] = pow(fac[MX – 1], MOD – 2, MOD)
    for i in range(MX – 1, 0, –1):
    invF[i – 1] = invF[i] * i % MOD

    init()

    # 从 n 个数中选 m 个数的方案数
    def comb(n, m):
    if m < 0 or m > n:
    return 0
    return fac[n] * invF[m] % MOD * invF[n – m] % MOD

    def count_valid_sequences(n, k):
    ans = comb(n – 1, k – 1)

    if (n + k) % 2 == 0:
    ans = (ans – comb((n + k) // 2 – 1, k – 1) + MOD) % MOD

    return ans

    if __name__ == "__main__":
    n = 5
    k = 3
    result = count_valid_sequences(n, k)
    print(result)

    在这里插入图片描述

    C++完整代码如下:

    #include <iostream>
    using namespace std;

    const int MOD = 1'000'000'007;
    const int MX = 500'000;

    int fac[MX]; // fac[i] = i!
    int invF[MX]; // invF[i] = i!^(-1) = pow(i!, MOD-2)

    int powmod(int x, int n) {
    long long res = 1;
    long long base = x;
    while (n > 0) {
    if (n & 1) res = res * base % MOD;
    base = base * base % MOD;
    n >>= 1;
    }
    return (int)res;
    }

    void init() {
    fac[0] = 1;
    for (int i = 1; i < MX; ++i) {
    fac[i] = (long long)fac[i – 1] * i % MOD;
    }

    invF[MX – 1] = powmod(fac[MX – 1], MOD – 2);
    for (int i = MX – 1; i > 0; —i) {
    invF[i – 1] = (long long)invF[i] * i % MOD;
    }
    }

    // 从 n 个数中选 m 个数的方案数
    int comb(int n, int m) {
    if (m < 0 || m > n) return 0;
    return (long long)fac[n] * invF[m] % MOD * invF[n – m] % MOD;
    }

    int countValidSequences(int n, int k) {
    int ans = comb(n – 1, k – 1);
    if ((n + k) % 2 == 0) {
    ans = (ans – comb((n + k) / 2 – 1, k – 1) + MOD) % MOD; // +MOD 保证答案非负
    }
    return ans;
    }

    int main() {
    init();

    int n = 5;
    int k = 3;
    int result = countValidSequences(n, k);
    cout << result << endl;

    return 0;
    }

    在这里插入图片描述

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 2026-10-25:统计有效序列数目。用go语言,给定两个正整数 n 和 k。现在要统计满足下面条件的有序序列数量:序列一共包含 k 个正整数,这些数的总和恰好为 n,并且这些数的乘积必须是偶数。序
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!