题意
- 给你
n
首不同的歌,有一个L
长的播放列表,让你这用这些歌,在满足某种条件的前提下,把播放列表填满,问有多少种填法
- 两个条件是:1. 每首歌至少用1次;2. 如果一个歌放在了第
i
个位置上,则下一次它最早只能出现在i+k+1
的位置上
思路1
- 在这一节里,我们主要讨论用容斥原理的做法,复杂度会比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 c(i)=n−k,i>kc(i)=n−i−1,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} R=i=k+1∑N(−1)n−iCnn−iAii−k(i−k)L−k
- 实现的时候几个注意点:(1)组合数里面有除法,我们通过计算逆元的方式保证同余计算下的正确性(2)可以预处理
n
以下的阶乘加速求解(3)幂运算通过快速幂计算
思路2
- 我们考虑用dp求解
- 这题的状态设计思路还是有一些意思的,和一些经典思路还不太一样
dp(i, j)
表示用j
首歌,覆盖前i
个位置方案数,这里注意是不是前j
首歌,而是n
个里面的任意j
首,这和一些经典思路还是不太一样的
- 递推方程:
d p ( i , j ) = d p ( i − 1 , j − 1 ) × ( n − j + 1 ) + d p ( i − 1 , j ) × max ( j − k , 0 ) dp(i, j) = dp(i-1, j-1) \times (n - j + 1) + dp(i-1, j) \times \max(j - k, 0) dp(i,j)=dp(i−1,j−1)×(n−j+1)+dp(i−1,j)×max(j−k,0)
- 稍微解释一些,第一项表示第
i
个位置要放新歌了,可以是除了前j-1
首的任意一首,第二项表示,前i-1
个位置已经放了i首歌了,第i
个位置可以从前j
首歌里选,但是要选之前的k
首歌不一样的
实现
容斥
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;
}
};
DP
class Solution {
typedef long long ll;
public:
static const ll MOD = 1e9+7;
int numMusicPlaylists(int N, int L, int K){
vector<ll> dp(N + 1);
dp[0] = 1;
for (int i = 1; i <= L; i++){
for (int j = min(N, i); j > 0; j--){
dp[j] = (dp[j-1] * (N - j + 1) + dp[j] * max(j - K, 0)) % MOD;
}
dp[0] = 0;
}
return dp[N];
}
};