[六省联考2017] 组合数问题
一、题目
二、解法
其实不一定是递推问题才想矩阵加速,比如某个东西很大,但是某个东西很小的时候就可以尝试矩阵乘法了。
这道题就用很小的量来定义状态就行了,设 \(f(i,j)\) 表示考虑了 \(i\) 个数,选的总数模 \(k\) 是 \(j\),那么每次就考虑这个数选还是不选,不难写出转移(第二维在模意义下):
\[f(i,j)=f(i-1,j)+f(i-1,j-1)
\]
这个就很容易矩阵乘法了,时间复杂度 \(O(k^3\log(nk))\),注意考虑下 \(k=1\) 的情况。
还有一个做法也是利用了快速幂去算这东西,只不过用的是多项式的快速幂。那么如果我们要去套多项式快速幂怎么办呢?这就要求我们找到沟通组合数和多项式的桥梁了,这里可以用二项式定理:
\[\begin{aligned}
&=\sum_{i\bmod k=r}{nk\choose i}\\
&=\sum_{i\bmod k=r}[x^i](1+x)^{nk}\\
&=[x^r]((1+x)^{nk}\bmod (x^k-1))
\end{aligned}
\]
直接循环卷积快速幂,时间复杂度 \(O(k^2\log (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\n",b[r]);
}