题意
1 ≤ n ≤ 10^9, 0 ≤ r < k ≤ 50, 2 ≤ p ≤ 2^30 − 1
分析
拿到题目就开始狂推式子,看了题解才发现原来是dp。
我们从直观上来理解我们要求的这个诡异的式子。
实际上就是要我们从
nk
件物品里面选出若干件,使得其数量模k等于r的方案数。
显然的dp方程
f[i,j]
表示前i件物品拿了若干件使得其数量模k等于j的方案数。
那么显然有
f[i,j]=f[i−1,j]+f[i−1,j−1]
矩阵乘法优化即可。
复杂度
O(k3logn)
还有一种更棒的做法,同样是dp,但可以发现
f[n∗2,i+j]+=f[n,i]∗f[n,j]
可以理解成枚举前n个物品的选法和后n个物品的选法。
那么直接对dp数组做快速幂即可。
复杂度
O(k2logn)
代码
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long LL;
const int N=55;
LL n,p,k,r;
struct arr{LL f[N];}ans,a;
void mul(arr &a,arr b,arr c)
{
memset(a.f,0,sizeof(a.f));
for (int i=0;i<k;i++)
for (int j=0;j<k;j++)
a.f[(i+j)%k]=(a.f[(i+j)%k]+b.f[i]*c.f[j]%p)%p;
}
void ksm(arr x,LL y)
{
y--;
while (y)
{
if (y&1) mul(ans,ans,x);
mul(x,x,x);y>>=1;
}
}
int main()
{
scanf("%lld%lld%lld%lld",&n,&p,&k,&r);
a.f[0]++;a.f[1%k]++;ans=a;
ksm(a,n*k);
printf("%lld",ans.f[r]);
return 0;
}