和为p点游戏

[题目链接](https://www.nowcoder.com/practice/4008dea63cda4087876ddb55feb207a9)

思路

个骰子,每轮同时投掷所有骰子并记录点数之和。可以投掷任意多轮(),求所有轮的总点数之和恰好为 的概率,答案对 取模。

分两步走

第一步:一轮投掷的概率分布

为一轮投掷 个骰子,点数之和恰好为 的概率。 的范围是

用经典 DP 求出 个骰子投出各种点数之和的方案数 ,然后

第二步:多轮累加的概率

为投掷若干轮( 轮),总点数之和恰好为 的概率。令 (零轮,和为 ),则递推关系为:

$$

含义:最后一轮得到 点,前面若干轮总和为

最终答案即 时自然排除了零轮的情况)。

为什么这是正确的?

从生成函数角度,设 为单轮的概率生成函数。投掷 轮对应 ,投掷任意多轮的概率生成函数为:

$$

恰好对应 的生成函数( 包含了零轮项),所以 项()就是

复杂度分析

  • 时间复杂度:。第一步计算 个骰子的方案数为 ,第二步递推 数组为
  • 空间复杂度:

代码

#include <bits/stdc++.h>
using namespace std;

const int MOD = 1e9 + 7;

long long qpow(long long a, long long b, long long mod) {
    long long res = 1;
    a %= mod;
    while (b > 0) {
        if (b & 1) res = res * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return res;
}

int main() {
    int n, p;
    scanf("%d%d", &n, &p);

    int maxS = min(6 * n, p);

    // 第一步:计算 n 个骰子投出各点数和的方案数
    vector<long long> dp(maxS + 1, 0);
    dp[0] = 1;
    for (int i = 0; i < n; i++) {
        vector<long long> ndp(maxS + 1, 0);
        for (int j = 0; j <= maxS; j++) {
            if (dp[j] == 0) continue;
            for (int face = 1; face <= 6 && j + face <= maxS; face++) {
                ndp[j + face] = (ndp[j + face] + dp[j]) % MOD;
            }
        }
        dp = ndp;
    }

    // 转为概率:f[s] = dp[s] / 6^n
    long long inv6n = qpow(qpow(6, n, MOD), MOD - 2, MOD);
    vector<long long> f(maxS + 1, 0);
    for (int s = n; s <= maxS; s++) {
        f[s] = dp[s] % MOD * inv6n % MOD;
    }

    // 第二步:递推 g[t] = sum f[s] * g[t-s]
    vector<long long> g(p + 1, 0);
    g[0] = 1;
    for (int t = 1; t <= p; t++) {
        for (int s = n; s <= min(maxS, t); s++) {
            g[t] = (g[t] + f[s] % MOD * g[t - s]) % MOD;
        }
    }

    printf("%lld\n", g[p]);
    return 0;
}
import java.util.Scanner;

public class Main {
    static final long MOD = 1_000_000_007;

    static long qpow(long a, long b, long mod) {
        long res = 1;
        a %= mod;
        while (b > 0) {
            if ((b & 1) == 1) res = res * a % mod;
            a = a * a % mod;
            b >>= 1;
        }
        return res;
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt(), p = sc.nextInt();

        int maxS = Math.min(6 * n, p);

        // 第一步:计算 n 个骰子投出各点数和的方案数
        long[] dp = new long[maxS + 1];
        dp[0] = 1;
        for (int i = 0; i < n; i++) {
            long[] ndp = new long[maxS + 1];
            for (int j = 0; j <= maxS; j++) {
                if (dp[j] == 0) continue;
                for (int face = 1; face <= 6 && j + face <= maxS; face++) {
                    ndp[j + face] = (ndp[j + face] + dp[j]) % MOD;
                }
            }
            dp = ndp;
        }

        // 转为概率:f[s] = dp[s] / 6^n
        long inv6n = qpow(qpow(6, n, MOD), MOD - 2, MOD);
        long[] f = new long[maxS + 1];
        for (int s = n; s <= maxS; s++) {
            f[s] = dp[s] % MOD * inv6n % MOD;
        }

        // 第二步:递推 g[t] = sum f[s] * g[t-s]
        long[] g = new long[p + 1];
        g[0] = 1;
        for (int t = 1; t <= p; t++) {
            for (int s = n; s <= Math.min(maxS, t); s++) {
                g[t] = (g[t] + f[s] % MOD * g[t - s]) % MOD;
            }
        }

        System.out.println(g[p]);
    }
}