【洛谷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\) 的一般公式:
其中 \(n! = 1 \times 2 \times \cdots \times n\)。(特别地,当 \(n = 0\) 时,\(n! = 1\);当 \(m < n\) 时,\(C_n^m = 0\)。)
小葱在 NOIP 的时候学习了 \(C_i^j\) 和 \(k\) 的倍数关系,现在他想更进一步,研究更多关于组合数的性质。小葱发现,\(C_i^j\) 是否是 \(k\) 的倍数,取决于 \(C_i^j \bmod k\) 是否等于 \(0\),这个神奇的性质引发了小葱对 \(\mathrm{mod}\) 运算(取余数运算)的兴趣。现在小葱选择了是四个整数 \(n, p, k, r\),他希望知道
即
的值。
\(1 \leq n \leq 10^9, 0 \leq r < k \leq 50, 2 \leq p \leq 2^{30} - 1\)。
思路
发现题目要求的就是
发现 \(n\) 很大,但是 \(k,r\) 很小,而我们只需要求对于 \(i\bmod p=r\) 的 \(i\) 的贡献,考虑矩阵乘法。
设 \(f_{i,j}\) 表示对于 \(\sum_{k\bmod p=j}\binom{i}{k}\) 的和。显然
注意这里第二维是 \(\bmod p\) 意义下的。
然后矩阵乘法优化就可以做到 \(O(k^3\log (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;
}