本文介绍: Leetcode 第 374 场双周赛 Problem D 100146. 统计感冒序列数目组合数学+阶乘+逆元题目给你一个整数 n 和一个下标从 0 开始的整数数组 sick数组升序 排序。有 n 位小朋友站成一排,按顺序编号为 0 到 n – 1 。数组 sick 包含一开始得了感冒小朋友位置。如果位置i小朋友得了感冒,他会传染给下标i – 1 或者 i + 1 的小朋友,前提 是被传染的小朋友存在且还没有感冒。每一秒中, 至多一位 还没感冒小朋友会被传染。
/**
     * 组合数学+阶乘+逆元:
     *
     * 第 1 步:
     * 分析题目易得每秒都 **有且仅仅有一位未感冒者** 被传染,且他的左或右一定有感冒的人,
     * 然后观察示例可以想到:未感冒者会组成 k 个连续区间,除了第一段以及看最后一段区间,中间区间全是左右均有感冒
     *
     * 第 2 步:
     * 因为已感冒者无法被影响,因此先在区间内部考虑,再思考区间与区间的关系,
     *
     * 第 3 步:
     * 区间内部方案数:
     *     * 第一段或者最后一段仅有 1 种方案数,从右到左与从左到右
     *     * 中间区间 pre[i] 个人、方案数为:2 ^ (pre[i]-1)
     *         * 暴力枚举也可
     *         * DP 也行:dp[i] 代表 i 个人方案数由 i-1 个人方案数选 首/尾,且最后一个人无法选择dp[i] = dp[i-1] * 2
     *
     * **第 4 步**:
     * 每段区间方案数相乘即 **总方案数**:1 * 2 ^(per[1]-1 + ... + per[k-1]-1)* 1
     * 但这仅是以整个区间来考虑的,如果形如:pre[0] 中选第一个pre[2] 中选最后一个、pre[1] 中选第一个...
     * 我们将其称为排列数,
     *
     * **第 5 步**:
     * 排序数:sum!/(per0!*per1!*...*perk!)
     * 设 pre[i] 总和为 sum每个区间 per[i] 只有 1 种排序方式(从前到后顺序放入),而区间内部如何选由方案数决定,
     * 接着我们又两种思考方式(公式化简后可以互相转化)
     *     1. 先不管 per 顺序、仅看 sum 的总排序数为 sum!,每种 per 的总排序数 per! 变成 1 种排序方式,结果就是:sum!/(per0!*per1!*...*perk!)
     *     2. 从 s 个空位中找到 per[0] 个位置顺序放入、结果为 C(s,per[0]),接着从 s-per[0] 个空位找 per[1] 个位置顺序放入、结果为 C(s-per[0],per[1]) ... ,
     *     总结果就是 C(s,per[0])*C(s-per[0],per[1])*...*C(s-per[0]-...-per[k-1],per[k])(可以化简为 sum!/(per[0]!*per[1]!*...*per[k]!))
     *
     * 第 6 步:
     * 结果就是:每种区间内部方案数 * 区间之间排列数
     *
     */
    public int numberOfSequence(int n, int[] sick) {
        long res = 1;

        // 排序数:sum!
        long perTotal = FAC[n - sick.length];

        // 排序数:per[0],代表第一个感冒左边多少个人
        int per = sick[0];
        perTotal *= INV_FAC[per];
        perTotal %= MOD;

        // 每段区间方案数相乘即 **总方案数**:1 * 2 ^(per[1]-1 + ... + per[k-1]-1)* 1
        int ansPow = 0;

        for (int i = 1; i < sick.length; i++) {
            // 排序数:sum!/(per0!*per1!*...*perk!)
            per = sick[i] - sick[i - 1] - 1;
            perTotal *= INV_FAC[per];
            perTotal %= MOD;

            // 中间的未感冒者有 2 ^ (per-1) 种方案数
            if (per > 0) {
                ansPow += per - 1;
            }
        }

        // 排序数:per[k],代表最后一个感冒右边多少个人
        per = n - sick[sick.length - 1] - 1;
        perTotal *= INV_FAC[per];
        perTotal %= MOD;

        // 每种区间内部方案数 * 区间之间排列
        res = qPow(2, ansPow, MOD) * perTotal % MOD;

        return (int)(res);
    }

    // 组合数模板
    private static final int MOD = (int)1e9+7;
    private static final int MX = 100_000;
    // 阶乘
    private static final long[] FAC = new long[MX];
    // 阶乘的逆元
    private static final long[] INV_FAC = new long[MX];

    static {
        FAC[0] = 1;
        for (int i = 1; i < MX; i++) {
            FAC[i] = FAC[i - 1] * i % MOD;
        }

        INV_FAC[MX - 1] = qPow(FAC[MX - 1], MOD - 2, MOD);
        for (int i = MX - 1; i > 0; i--) {
            INV_FAC[i - 1] = INV_FAC[i] * i % MOD;
        }
    }

    private static long qPow(long value, long pow, long mod) {
        long res = 1;
        while (pow > 0) {
            if ((pow &amp; 1) == 1) {
                res *= value;
                res %= mod;
            }

            value *= value;
            value %= mod;
            pow >>= 1;
        }
        return res;
    }

原文地址:https://blog.csdn.net/qq_33530115/article/details/134772278

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任

如若转载,请注明出处:http://www.7code.cn/show_36198.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱:suwngjj01@126.com进行投诉反馈,一经查实,立即删除

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注