[BZOJ 4870] 组合数问题
Link:
Solution:
组合数的式子都可以先想想能不能递推,写出来就是:
$\sum C_{n*k}^{i*k+r}=\sum C_{n*k-1}^{i*k+r}+\sum C_{n*k-1}^{i*k+r-1}$
如果将每个求和看成一个整体,设$dp[n][r]=\sum C_{n}^{i*k+r}$,
则有$dp[n][r]=dp[n-1][r]+dp[n-1][(r-1+k)modk]$
由于$r$就相当于余数因此0-1后要变为$k-1$!
这样的递推式明显可以矩乘,直接上的话就是:
$新列向量=n*n矩阵\times 原列向量$,第$i$行将$s[i][i],s[i][(i-1+k)modk]$置1即可
不过注意这是一个循环矩阵,那么其实只要计算第一列,其他列都是其转动的结果
对于某一列有贡献的只有$n^2$个乘积,如果将每一对都转化成第一列的坐标发现是:
$s[k]=\sum_i \sum_j [(i+j)modn==k]s[i]*s[j]$ (下标从0开始)
而之所以$答案列向量\times 第一列$也是这个式子感觉要从算贡献来考虑,可能是个巧合?
Code:
#include <bits/stdc++.h> using namespace std; #define X first #define Y second #define pb push_back typedef double db; typedef long long ll; typedef pair<int,int> P; const int MAXN=55; int n,p,r,k,res[MAXN],a[MAXN],t[MAXN]; void mul(int *x,int *y) { memset(t,0,sizeof(t)); for(int i=0;i<=k;i++) for(int j=0;j<=k;j++) (t[(i+j)%k]+=1ll*x[i]*y[j]%p)%=p; for(int i=0;i<=k;i++) x[i]=t[i]; } int main() { scanf("%d%d%d%d",&n,&p,&k,&r); res[0]=1;a[0]++;a[1%k]++; for(ll idx=1ll*n*k;idx;idx>>=1,mul(a,a)) if(idx&1) mul(res,a); printf("%d",res[r]); return 0; }