Leetcode 920. Number of Music Playlists 容斥原理(O(N log L))
阿新 • • 發佈:2018-11-11
題意
- 給你
n
首不同的歌,有一個L
長的播放列表,讓你這用這些歌,在滿足某種條件的前提下,把播放列表填滿,問有多少種填法 - 兩個條件是:1. 每首歌至少用1次;2. 如果一個歌放在了第
i
個位置上,則下一次它最早只能出現在i+k+1
的位置上
思路
- 這個題可以dp求解,思路也是非常巧妙,我之後會補充上來
- 這裡主要討論用容斥原理的做法,複雜度會比dp的來的低一些
- 我們先考慮,如果沒有第一個條件,只有第二個條件是怎麼樣的情況呢?
- 這樣就非常簡單,對於第
i
首歌來說,它有幾種選擇呢?很顯然,因為每k
個位置的歌都不相同,所以有如下結論(設選擇數為c
- 這樣我們可以直接連乘,就可以算出方法數
- 接下來,我們考慮第二個條件。直觀來想,n個歌,每個歌至少用一次的方法數 = n個歌不限制用幾次 - 至少有一個歌沒出現的方法數(它也就是n-1個不限制用幾次的方法數 * 是哪個歌沒出現的種類數)
- 當然直接這麼算,是有問題的,因為會減重,所以我們應該加上至少兩個歌沒出現的方法數,然後又加重了,… 這是直觀解釋,其實就是要用到容斥原理了
- 最後的計算公式是:
- 實現的時候幾個注意點:(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;
}
};