写数学题总是没有思路,还是太菜了。
题目大意:给定n,p,k,r,你要求的是ΣC(nk,x)%p的值,其中x%k=r。n是1e9,k是50,p是1~2^32中任意一个数,不保证为质数。
这道题难在思路的转化,如果你在思考怎么快速求组合数,什么O(n)预处理,O(1)求组合数,那么你已经跑偏了,显然NK这个你O(n)也跑不过。这个要求的组合数也没什么特殊的性质,不会是什么结论题。那唯一的可以发现的方向就是如何把问题转化为递推、矩阵加速递推。设f(i,j)表示ΣC(i,x)%p,x%k=j。由杨辉三角递推式,其实可以转化成f(i,j)=f(i-1,j)+f(i-1,j-1).当然你还得考虑j=0时,回归原式,你发现其实应该是f(i,j)=f(i-1,j)+f(i-1,(j-1+k)%k)。那么我们可以设置一个1*k的状态矩阵F和一个k*k的状态转移矩阵A,显然我们需要乘A的nk次幂,用快速幂就好了。最后输出F【r】就是答案。
#include<bits/stdc++.h> #define ll long long using namespace std; int n,r,k,p,a[50][50],f[50][50],c[50][50],ans[50][50]; void mul(int b[50][50],int d[50][50]){ memset(c,0,sizeof(c)); for(int i=0;i<k;++i) for(int j=0;j<k;++j) for(int l=0;l<k;++l) c[i][j]=(c[i][j]+1LL*b[i][l]*d[l][j]%p)%p; for(int i=0;i<k;++i) for(int j=0;j<k;++j) b[i][j]=c[i][j]; } int main(){ scanf("%d%d%d%d",&n,&p,&k,&r); for(int i=0;i<=k-2;++i) a[i][i]=a[i][i+1]=1; a[k-1][k-1]++,a[k-1][0]++; //这里一定要注意,为什么使用++而不直接赋值为1,因为你要考虑k=1的情况 f[0][0]=1; ll siz=1LL*n*k; for(int i=0;i<k;++i) ans[i][i]=1; for(;siz;siz>>=1){ if(siz&1) mul(ans,a); mul(a,a); } mul(f,ans); printf("%d\n",f[0][r]); return 0; }