【洛谷P3746】组合数问题

题目

题目链接:https://www.luogu.com.cn/problem/P3746
组合数 (C_n^m) 表示的是从 (n) 个互不相同的物品中选出 (m) 个物品的方案数。举个例子, 从 ((1, 2, 3)) 三个物品中选择两个物品可以有 ((1, 2))((1, 3))((2, 3)) 这三种选择方法。根据组合数的定义,我们可以给出计算组合数 (C_n^m) 的一般公式:

[C_n^m = frac {n!} {m! (n - m)!} ]

其中 (n! = 1 imes 2 imes cdots imes n)。(特别地,当 (n = 0) 时,(n! = 1);当 (m < n) 时,(C_n^m = 0)。)
小葱在 NOIP 的时候学习了 (C_i^j)(k) 的倍数关系,现在他想更进一步,研究更多关于组合数的性质。小葱发现,(C_i^j) 是否是 (k) 的倍数,取决于 (C_i^j mod k) 是否等于 (0),这个神奇的性质引发了小葱对 (mathrm{mod}) 运算(取余数运算)的兴趣。现在小葱选择了是四个整数 (n, p, k, r),他希望知道

[left( sum_{i = 0}^infty C_{nk}^{ik + r} ight) mod p, ]

[left( C_{nk}^{r} + C_{nk}^{k + r} + C_{nk}^{2k + r} + cdots + C_{nk}^{(n - 1)k + r} + C_{nk}^{nk + r} + cdots ight) mod p ]

的值。
(1 leq n leq 10^9, 0 leq r < k leq 50, 2 leq p leq 2^{30} - 1)

思路

发现题目要求的就是

[sum_{imod n=r}inom{nk}{i}mod p ]

发现 (n) 很大,但是 (k,r) 很小,而我们只需要求对于 (imod p=r)(i) 的贡献,考虑矩阵乘法。
(f_{i,j}) 表示对于 (sum_{kmod p=j}inom{i}{k}) 的和。显然

[f_{i,j}=f_{i-1,j}+f_{i-1,j-1} ]

注意这里第二维是 (mod p) 意义下的。
然后矩阵乘法优化就可以做到 (O(k^3log (nk)))

代码

#include <bits/stdc++.h>
#define int long long
using namespace std;
typedef long long ll;

const int N=52;
int n,p,k,r;

struct Matrix
{
	int b[N][N];
	
	friend Matrix operator *(Matrix a,Matrix b)
	{
		Matrix c;
		memset(c.b,0,sizeof(c.b));
		for (int i=0;i<N;i++)
			for (int j=0;j<N;j++)
				for (int k=0;k<N;k++)
					c.b[i][j]=(c.b[i][j]+1LL*a.b[i][k]*b.b[k][j])%p;
		return c;
	}
}a,f;

signed main()
{
	scanf("%lld%lld%lld%lld",&n,&p,&k,&r);
	for (int i=0;i<k;i++)
		a.b[i][i]++,a.b[i][(i+1)%k]++;
	f.b[0][0]=1;
	for (ll i=n*k;i;i>>=1,a=a*a)
		if (i&1) f=f*a;
	printf("%lld",f.b[0][r]);
	return 0;
}
原文地址:https://www.cnblogs.com/stoorz/p/14427687.html