「一本通 6.6 练习 1」牡牛和牝牛

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

线性 DP 计数:按前缀末位性别转移,以牝牛结尾等于总方案数,以牡牛结尾回看 K+1 位之前的总方案数,O(N) 递推取模。

OJ: roj

题目 ID: 1652

难度:普及-

标签:动态规划计数DPpython

创建: 2026-10-01 00:09

更新: 2026-10-06 01:41

题目描述

NN 只牛(牡牛/牝牛相同)排队,任意两只牡牛之间至少 KK 只牝牛,求合法方案数对 50000115000011 取模(1≤N≤1051 \le N \le 10^5,0≤K<N0 \le K < N)。

思路

设 sis_i 为长度 ii 的方案数:末位放牝牛贡献 si−1s_{i-1},放牡牛时若 i≤Ki \le K 贡献 1、否则贡献 si−K−1s_{i-K-1},两者相加取模,答案为 sNs_N。

参考代码

Python 版:

python
"""排队方案数:任意两只牡牛之间至少 K 只牝牛。O(N) DP,取模 5000011。"""
import sys

def main() -> None:
    n, k = map(int, sys.stdin.read().split())
    MOD = 5000011
    # b[i]: 长 i 且以牡牛结尾的方案数; c[i]: 以牝牛结尾; s[i]: 总方案数
    b = [0] * (n + 1)
    c = [0] * (n + 1)
    s = [0] * (n + 1)
    s[0] = 1
    for i in range(1, n + 1):
        b[i] = s[i - k - 1] if i > k else 1   # 结尾放牡牛: 上一头牡牛最远到 i-k-1,否则它是第一头
        c[i] = s[i - 1]                       # 结尾放牝牛: 任意合法前缀延长
        s[i] = (b[i] + c[i]) % MOD
    print(s[n] % MOD)

main()

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 01:39
 * update_at: 2026-10-06 01:39
 */
#include <iostream>
#include <vector>
using namespace std;

typedef long long ll;

const ll MOD = 5000011;

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

    ll n, k;
    cin >> n >> k;

    // s[i] 表示长度 i 的合法方案总数
    vector<ll> s(n + 1);
    s[0] = 1; // 空前缀

    for (ll i = 1; i <= n; i++) {
        ll c = s[i - 1]; // 以牝牛结尾:前 i-1 位任意合法方案
        ll b;
        if (i <= k) {
            b = 1; // 以牡牛结尾且它是第一头牡牛
        } else {
            b = s[i - k - 1]; // 上一头牡牛至少在前 i-k-1 位
        }
        s[i] = (b + c) % MOD;
    }

    cout << s[n] << "\n";
    return 0;
}