1. 程式人生 > >Leetcode 920. Number of Music Playlists 容斥原理(O(N log L))

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

題意

  • 給你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;
    }
};