题目
题目链接: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;
}