[Daimayuan]01序列2(C++,数学)

又是大家最喜欢的 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 1n106

0 ≤ k < n 0≤k0k<n

样例输入

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(i1)ki 0 0 0分配到这些区间中

这时我们想到了数学组合数问题中常见的隔板法

隔板法:有 i i i个相同的物品,要求分成 j j j组,问有多少种分配方式(每组中至少分配 1 1 1个物品)

计算公式就是 C i − 1 j − 1 C_{i-1}^{j-1} Ci1j1

那么为了使用这个方法,我们从 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(i1)ki+i+1)1(i+1)1

现在新的问题出现了,计算组合数 C n m C_n^m Cnm需要计算 n ! m ! ( n − m ) ! \frac{n!}{m!(n-m)!} m!(nm)!n!,我们都知道阶乘会爆精度,所以想要取模

但是取模操作是不能对除法使用的

这时候就需要引入逆元

逆元:在 m o d   p mod\ p mod p的意义下,有 ( a ∗ b )   m o d   p ≡ 1 (a*b)\ mod\ p\equiv1 (ab) mod p1,我们称 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=ainv(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!(nm)!n! mod p=n!inv(m!)inv((nm)!) mod p

接下来是最后一个问题:如何求逆元

我在这里提供两个方法

(1)费马小定理: b p − 1   m o d   p = 1 b^{p-1}\ mod\ p=1 bp1 mod p=1

所以 b p − 2 b^{p-2} bp2即为 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;
}

你可能感兴趣的:(数学,c++,数学,组合数,逆元)