1. 程式人生 > >[BZOJ 4870] 組合數問題

[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; }