又是大家最喜欢的 01 01 01序列问题了呢
这次的问题非常的简单, c c cc cc觉得一个 01 01 01序列中两个1之间至少要有 k k k个 0 0 0,现在他要构造出一个长度为 n n n的 01 01 01序列,请问他有多少种不同的构造方法
这个数字可能会非常大,请你对 1 0 9 + 7 10^9+7 109+7取模
一行,给出两个整数 n n n, k k k
一个整数,代表不同的构造方法数
1 ≤ n ≤ 1 0 6 1≤n≤10^6 1≤n≤106
0 ≤ k < n 0≤k
4 2
6
首先理解一下题意:符合要求的 01 01 01序列中可能有 [ 0 , n + k k + 1 + 1 ] [0,\frac{n + k}{k + 1}+1] [0,k+1n+k+1]个 1 1 1,每两个 1 1 1之间必须有 k k k个 0 0 0
那么我们枚举 1 1 1的数量,累计含有不同 1 1 1的 01 01 01序列总数即可
接下来说明如何枚举
枚举 1 1 1的数量很简单,直接 f o r for for循环
然后是计算含有 i i i个 1 1 1的 01 01 01序列的种类数
由于每两个 1 1 1之间必须有 k k k个 0 0 0,我们将其提前分配出去以求简化问题
那么现在有 i + 1 i+1 i+1个区间,我们要将 n − ( i − 1 ) ∗ k − i n-(i-1)*k-i n−(i−1)∗k−i个 0 0 0分配到这些区间中
这时我们想到了数学组合数问题中常见的隔板法
隔板法:有 i i i个相同的物品,要求分成 j j j组,问有多少种分配方式(每组中至少分配 1 1 1个物品)
计算公式就是 C i − 1 j − 1 C_{i-1}^{j-1} Ci−1j−1
那么为了使用这个方法,我们从 i + 1 i+1 i+1个区间中抽取出 1 1 1个 0 0 0(因为要求每组中至少分配一个物品)
所以我们的计算公式变成了 C ( n − ( i − 1 ) ∗ k − i + i + 1 ) − 1 ( i + 1 ) − 1 C_{(n-(i-1)*k-i+i+1)-1}^{(i+1)-1} C(n−(i−1)∗k−i+i+1)−1(i+1)−1
现在新的问题出现了,计算组合数 C n m C_n^m Cnm需要计算 n ! m ! ( n − m ) ! \frac{n!}{m!(n-m)!} m!(n−m)!n!,我们都知道阶乘会爆精度,所以想要取模
但是取模操作是不能对除法使用的
这时候就需要引入逆元了
逆元:在 m o d p mod\ p mod p的意义下,有 ( a ∗ b ) m o d p ≡ 1 (a*b)\ mod\ p\equiv1 (a∗b) mod p≡1,我们称 b b b为 a a a的乘法逆元
那么这和我们遇到的问题有什么关系呢?
逆元(为了说明方便,之后称 i n v ( x ) inv(x) inv(x)为 x x x的逆元)有这样一个性质: a / b m o d p = a ∗ i n v ( b ) m o d p a/b\ mod\ p=a*inv(b)\ mod\ p a/b mod p=a∗inv(b) mod p
将除法取模转换为乘法取模,我们的问题就解决了
n ! m ! ( n − m ) ! m o d p = n ! ∗ i n v ( m ! ) ∗ i n v ( ( n − m ) ! ) m o d p \frac{n!}{m!(n-m)!}\ mod\ p=n!*inv(m!)*inv((n-m)!)\ mod\ p m!(n−m)!n! mod p=n!∗inv(m!)∗inv((n−m)!) mod p
接下来是最后一个问题:如何求逆元
我在这里提供两个方法
(1)费马小定理: b p − 1 m o d p = 1 b^{p-1}\ mod\ p=1 bp−1 mod p=1
所以 b p − 2 b^{p-2} bp−2即为 m o d p mod\ p mod p意义下, b b b的乘法逆元
long long pow(long long x, long long y, long long p) {//快速幂
long long ret = 1;
while (y) {
if (y & 1) ret = (ret * x) % p;
ret = (ret * ret) % p;
y >>= 1;
}
return ret;
}
long long inv(long long x, long long p) {//mod p意义下,x的逆元
return pow(x, p - 2, p);
}
(2)扩展欧几里得
void exgcd(long long a, long long b, long long& x, long long& y) {//扩展欧几里得
if (!b) {
x = 1;
y = 0;
}
else {
exgcd(b, a % b, y, x);
y = y - x * (a / b);
}
}
long long inv(long long a, long long b) {
long long x, y;//x为mod b意义下,a的逆元;y为mod a意义下,b的逆元
exgcd(a, b, x, y);
return (x + b) % b;
}
由于写的只是题解,所以只说明了功能,并未讲解原理,如果有对逆元的证明感兴趣的读者,这里推荐一篇乘法逆元的博客
最后,AC代码如下
#include
using namespace std;
const int max_n = 1e6;
const int max_k = max_n;
const long long mod_num = 1e9 + 7;
int n, k;
long long factorial[max_n + 1];
long long pow(long long x, long long y, long long p) {//快速幂
long long ret = 1;
while (y) {
if (y & 1) ret = (ret * x) % p;
x = (x * x) % p;
y >>= 1;
}
return ret;
}
long long inv(long long x, long long p) {//mod p意义下,x的逆元
return pow(x, p - 2, p);
}
long long cmd(long long m, long long n) {
long long num1 = factorial[n];
long long num2 = factorial[m];
long long inv2 = inv(num2, mod_num);
long long num3 = factorial[n - m];
long long inv3 = inv(num3, mod_num);
return ((num1 * inv2) % mod_num) * inv3 % mod_num;
}
int main() {
factorial[0] = 1;
for (int i = 1; i <= max_n; i++) factorial[i] = factorial[i - 1] * i % mod_num;
cin >> n >> k;
int max_ones = (n + k) / (k + 1);
long long ans = 1;
for (int i = 1; i <= max_ones; i++) {
int left = n - (i - 1) * k + 1;
ans = (ans + cmd((i + 1) - 1, left - 1)) % mod_num;
}
cout << ans << endl;
return 0;
}