跳转至

BISHI72 中位数之和

较难通过率 35.22%python3样例通过牛客 AC

牛客原题  源码

讲解章节基础数学与递推分治进阶:整体二分与 CDQ

一句话

01 数组的所有长度为 k(奇数)的子序列,中位数之和 mod (1e9+7)。

解题思路

这题考什么

「0/1 值 => 求和 == 计数」+ 组合数。

子序列元素只有 0 和 1,长度 k 为奇数,中位数 = 排序后第 (k+1)/2 个。 中位数为 1 <=> 子序列里 1 的个数 >= (k+1)/2 (记 h = (k+1)//2)。 因为中位数只能是 0 或 1,「中位数之和」= 「中位数为 1 的子序列个数」。

设数组里共有 c1 个 1、c0 = n - c1 个 0,答案为

∑_{j=h}^{min(k, c1)}  C(c1, j) * C(c0, k - j)

(从 1 里挑 j 个、从 0 里挑 k-j 个;j 也要满足 k-j <= c0)。 注意:这里按位置计数,同值元素在不同下标算作不同子序列,正是题意。

验算样例:n=5,k=1,全是 1 -> c1=5,c0=0,h=1,j=1:C(5,1)*C(0,0)=5 ✓

数据规模与复杂度

t <= 1e4,∑n <= 2e5,k <= n。 预处理阶乘/阶乘逆元到 2e5(一次,全局),每组循环 O(k) <= O(n), 总复杂度 O(maxn + ∑n)。 枚举「子序列本身」是 C(n,k) 级别的天文数字,只能用组合计数。

坑在哪

  1. 逐组重建阶乘表会退化成 O(t * n),必须在最外层建一次表; 表长取所有测试用例里最大的 n 即可;
  2. 求和上界是 min(k, c1),下界还要满足 k-j <= c0 即 j >= k-c0, 统一用 C(x,y)=0 (y>x 或 y<0) 兜住最省心;
  3. 中位数是排序后第 (k+1)/2 个(1-indexed),k 为奇数时下标 h=(k+1)//2;
  4. 每组 n 可能很小但 t 很大,IO 必须整块读。

参考实现

solutions/BISHI72.py
import sys

P = 1000000007


def main() -> None:
    data = sys.stdin.buffer.read().split()
    t = int(data[0])
    idx = 1
    cases = []
    mx = 1
    # 先把所有测试用例扫一遍:只留下 (n, k, 1 的个数),顺便记录最大的 n
    for _ in range(t):
        n = int(data[idx]); k = int(data[idx + 1]); idx += 2
        c1 = 0
        for j in range(idx, idx + n):
            if data[j] == b"1":       # data 里是 bytes,与 bytes 字面量比较,不必转 int
                c1 += 1
        idx += n                      # 游标跳过这一组的 n 个元素
        cases.append((n, k, c1))
        if n > mx:
            mx = n                    # 阶乘表只需开到所有用例中最大的 n

    fact = [1] * (mx + 1)                     # 全局只建一次
    for i in range(2, mx + 1):
        fact[i] = fact[i - 1] * i % P
    inv_fact = [1] * (mx + 1)
    inv_fact[mx] = pow(fact[mx], P - 2, P)
    for i in range(mx, 0, -1):
        inv_fact[i - 1] = inv_fact[i] * i % P

    def C(a: int, b: int) -> int:
        if b < 0 or b > a:
            return 0                          # 越界一律返回 0,省掉调用处的边界讨论
        return fact[a] * inv_fact[b] % P * inv_fact[a - b] % P

    out = []
    for n, k, c1 in cases:
        c0 = n - c1
        h = (k + 1) // 2                      # 至少要有 h 个 1,中位数才是 1
        s = 0
        # 枚举子序列里 1 的个数 j:从 1 中挑 j 个、从 0 中挑 k-j 个
        # 上界 min(k, c1) 卡住「1 不够挑」;下界不足的情形由 C 返回 0 兜住
        for j in range(h, min(k, c1) + 1):
            s += C(c1, j) * C(c0, k - j) % P
        out.append(str(s % P))                # 累加时不逐项取模,末尾统一收一次
    sys.stdout.write("\n".join(out) + "\n")


main()
[:octicons-arrow-left-16: BISHI71](BISHI71.md) [BISHI73 :octicons-arrow-right-16:](BISHI73.md)