题意:
求 (C_{nk}^r+C_{nk}^{k+r}dots+C_{nk}^{nk+r}dots) 的和 (mod p) 的值
范围&性质: (1le n,ple 10^9,1le r< kle 50)
分析:
- 暴力
各种特判+Lucas+暴力组合数 ( o) 80pts 这省选真良心
- 正解
由于 (r,k) 很小,所以要么容斥掉不合法情况,要么就矩阵乘优化递推
但是单纯的矩阵乘每次会漏掉一些信息,比如无法由 (f_{i-1,j-1}) 直接更新出 (f_{i,j-1}) ,因为缺少了一项 (f_{i-1,j-2})
所以构造的矩阵乘法是类似循环的,即第 (k) 个数更新后会变成下一次矩乘中的第一项,这样就不会存在信息被漏掉的情况,因为原来的复杂度(O(k^3 log nk))
其实有更优秀的(O(k^2log nk))的做法
代码:
#include<bits/stdc++.h>
using namespace std;
namespace zzc
{
inline int read()
{
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
const int maxn = 55;
int n,k,mod,r;
struct mat
{
long long c[maxn][maxn];
mat(){memset(c,0,sizeof(c));}
mat operator *(const mat &b)const
{
mat res;
for(int i=1;i<=k;i++)
{
for(int j=1;j<=k;j++)
{
for(int l=1;l<=k;l++)
{
res.c[i][l]=(res.c[i][l]+c[i][j]*b.c[j][l])%mod;
}
}
}
return res;
}
}trans,ans;
mat qpow(mat x,long long y)
{
mat res;
for(int i=1;i<=k;i++) res.c[i][i]=1;
while(y)
{
if(y&1) res=res*x;
x=x*x;
y>>=1;
}
return res;
}
void work()
{
n=read();mod=read();k=read();r=read();
for(int i=1;i<=k;i++)
{
trans.c[i][i]=1;
if(i!=k) trans.c[i+1][i]+=1;
else trans.c[1][i]+=1;
}
for(int i=1;i<=k;i++) ans.c[i][i]=1;
ans=ans*qpow(trans,1ll*n*k);
printf("%lld
",ans.c[r+1][1]);
}
}
int main()
{
zzc::work();
return 0;
}