一、题目
二、解法
其实不一定是递推问题才想矩阵加速,比如某个东西很大,但是某个东西很小的时候就可以尝试矩阵乘法了。
这道题就用很小的量来定义状态就行了,设 (f(i,j)) 表示考虑了 (i) 个数,选的总数模 (k) 是 (j),那么每次就考虑这个数选还是不选,不难写出转移(第二维在模意义下):
[f(i,j)=f(i-1,j)+f(i-1,j-1)
]
这个就很容易矩阵乘法了,时间复杂度 (O(k^3log(nk))),注意考虑下 (k=1) 的情况。
还有一个做法也是利用了快速幂去算这东西,只不过用的是多项式的快速幂。那么如果我们要去套多项式快速幂怎么办呢?这就要求我们找到沟通组合数和多项式的桥梁了,这里可以用二项式定理:
[egin{aligned}
&=sum_{imod k=r}{nkchoose i}\
&=sum_{imod k=r}[x^i](1+x)^{nk}\
&=[x^r]((1+x)^{nk}mod (x^k-1))
end{aligned}
]
直接循环卷积快速幂,时间复杂度 (O(k^2log (nk))),不会真的有人无聊到去写多项式乘法吧。
#include <cstdio>
const int M = 1005;
#define int long long
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,p,k,r,a[M],b[M],A[M],B[M];
void mul(int *a,int *b)
{
for(int i=0;i<k;i++) A[i]=a[i],B[i]=b[i],b[i]=0;
for(int i=0;i<k;i++)
for(int j=0;j<k;j++)
b[(i+j)%k]=(b[(i+j)%k]+A[i]*B[j])%p;
}
signed main()
{
n=read();p=read();k=read();r=read();
if(k==1) a[0]=2%p;//这时候都在常数项
else a[0]=a[1]=1;
n*=k;b[0]=1;
while(n>0)
{
if(n&1) mul(a,b);
mul(a,a);
n>>=1;
}
printf("%lld
",b[r]);
}