Leetcode 920. Number of Music Playlists 容斥原理(O(N log L))

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/luke2834/article/details/83958389

题意

  • 给你n首不同的歌,有一个L长的播放列表,让你这用这些歌,在满足某种条件的前提下,把播放列表填满,问有多少种填法
  • 两个条件是:1. 每首歌至少用1次;2. 如果一个歌放在了第i个位置上,则下一次它最早只能出现在i+k+1的位置上

思路

  • 这个题可以dp求解,思路也是非常巧妙,我之后会补充上来
  • 这里主要讨论用容斥原理的做法,复杂度会比dp的来的低一些
  • 我们先考虑,如果没有第一个条件,只有第二个条件是怎么样的情况呢?
  • 这样就非常简单,对于第i首歌来说,它有几种选择呢?很显然,因为每k个位置的歌都不相同,所以有如下结论(设选择数为c):
    c ( i ) = n k , i > k c ( i ) = n i 1 , i < = k c(i) = n - k, \quad i > k \\ c(i) = n - i - 1, \quad i <= k
  • 这样我们可以直接连乘,就可以算出方法数
  • 接下来,我们考虑第二个条件。直观来想,n个歌,每个歌至少用一次的方法数 = n个歌不限制用几次 - 至少有一个歌没出现的方法数(它也就是n-1个不限制用几次的方法数 * 是哪个歌没出现的种类数)
  • 当然直接这么算,是有问题的,因为会减重,所以我们应该加上至少两个歌没出现的方法数,然后又加重了,… 这是直观解释,其实就是要用到容斥原理了
  • 最后的计算公式是:
    R = i = k + 1 N ( 1 ) n i C n n i A i i k ( i k ) L k R = \sum_{i=k+1}^N (-1)^{n-i} C_n^{n-i}A_i^{i-k}(i-k)^{L - k}
  • 实现的时候几个注意点:(1)组合数里面有除法,我们通过计算逆元的方式保证同余计算下的正确性(2)可以预处理n以下的阶乘加速求解(3)幂运算通过快速幂计算

实现

class Solution {
    typedef long long ll;
public:
    static const ll MOD = 1e9+7;
    ll extgcd(ll a,ll b,ll& x,ll& y){
        if (b != 0){
            ll ret = extgcd(b,a%b,y,x);
            y -= a / b * x; 
            return ret;
        }
        else{
            x = 1, y = 0;
            return a;
        }
    }
    ll mod_inverse(ll a){
        ll x, y;
        extgcd(a, MOD, x, y);
        return (MOD + x % MOD) % MOD;
    }
    ll fact[101];
    void cal_fact(int n){
        fact[0] = 1;
        for (int i = 1; i <= n; i++){
            fact[i] = (fact[i-1] * ll(i)) % MOD;
        }
    }
    ll mod_comb(ll n, ll k){
        if (n < 0 || k < 0 || n < k)
            return 0;
        ll a1 = fact[n], a2 = fact[k], a3 = fact[n-k];
        return a1 * mod_inverse(a2 * a3 % MOD) % MOD;
    }
    ll mod_pow(ll x, ll n){
        ll res = 1;
        while (n > 0){
            if (n & 1) 
                res = res * x % MOD;
                x = x * x % MOD;
            n >>= 1;
        }
        return res;
    }
    int numMusicPlaylists(int N, int L, int K) {
        cal_fact(N);
        ll res = 0;
        for (int i = N; i > K; i--){
            ll now = N - i & 1 ? -1 : 1;
            now = (now * mod_comb(N, i)) % MOD;
            now = (now * mod_pow(i - K, L - K)) % MOD;
            now = (now * fact[i]) % MOD;
            now = (now * mod_inverse(fact[i-K])) % MOD;
            res = (res + now + MOD) % MOD;
        }
        return res;
    }
};

猜你喜欢

转载自blog.csdn.net/luke2834/article/details/83958389