[六省联考2017]组合数问题
做题时间:2022.7.6
\(【题目描述】\)
给定正整数 \(n,p,k,r(1\leq n\leq 10^9,0\leq r<k\leq 50,2\leq p\leq 2^{30}-1)\) ,求:
\(【输入格式】\)
一行四个整数 \(n,p,k,r\)
\(【输出格式】\)
一行一个整数表示答案
\(【考点】\)
数学、组合、递推、矩阵快速幂
\(【做法】\)
直接用代数方法硬凑行不通,各个项模 \(p\) 的余数也没有规律,可以考虑其实际意义
\(C_{nk}^{ik+r}\) 表示 \(nk\) 个物品里选取 \(ik+r\) 个的方案数, 而 \(ik+r(0\leq i< \inf)\) 非常类似于一个带余除法的逆运算,原式中 \(i\) 一直在变化,可以看成商,对应地 \(k\) 可以看成除数, \(r\) 可以看成余数。这样原式就有了实际意义——在 \(nk\) 个物品中选出 \(\mod k\) 余 \(r\) 个数的总方案数(其中的每一项就代表了 \(\mod k\) 不同的商)
重新定义: \(f_{i,j}\) 表示在 \(nk\) 个物品中选出 \(\mod k\) 余 \(r\) 个数的总方案数,也就是原式,类似于组合数的递推,考虑第 \(i\) 个数有选和不选两种情况,选的方案数为 \(f_{i-1,j-1}\) ,不选的方案数为 \(f_{i-1,j}\) ,递推式就是:
特殊情况是,当 \(j=0\) 时,转移方程变为:
要求的就是 \(f_{nk,r}\)
由于 \(nk\) 很大,考虑矩阵加速,每一个 \(f_{i}\) 都与 \(f_{i-1}\) 有关,因此考虑从 \(f_{0,j}\) 推至 \(f_{nk,j}\) ,其中 \(0\leq j <k\) (因为 \(f_{i,0}\) 与 \(f_{i-1,k}\) 相关且 \(r<k\) )。
这样就可以求出方程了。
另外要注意一点,当 \(k=1\) 时,矩阵就变成一个数了,此时底数矩阵应当为 \([2]\) ,这个点直接将赋初值变成累加即可。
其中 \(f_{0,0}=1,f_{0,i}=0(1\leq i\leq k-1)\)
\(【代码】\)
#include<cstdio>
#include<cstring>
using namespace std;
const int N=55;
typedef long long ll;
ll n,p,K,R;
struct Matrix{
ll a[N][N];
Matrix(){memset(a,0,sizeof(a)); }
Matrix operator *(const Matrix b) const{
Matrix c;
for(int i=0;i<K;i++){
for(int j=0;j<K;j++){
for(int k=0;k<K;k++){
c.a[i][j]=(c.a[i][j]+a[i][k]*b.a[k][j]%p)%p;
}
}
}
return c;
}
}A,B,One;
void Init()
{
for(int i=0;i<K;i++) One.a[i][i]=1;
B.a[0][0]=1;
for(int i=0;i<K;i++) A.a[i][(i-1+K)%K]++,A.a[i][i]++;
//当k=1时,(i-1+k)%k=i, a[i][i]=2
//当i=0时,(i-1+k)%k=k-1
}
Matrix FastPow(Matrix x,ll b)
{
Matrix ans=One;
while(b){
if(b&1) ans=ans*x;
x=x*x;
b>>=1;
}
return ans;
}
int main()
{
scanf("%d%d%d%d",&n,&p,&K,&R);
Init();
Matrix ans=FastPow(A,(ll)n*K);
printf("%lld\n",ans.a[0][R]);
return 0;
}

浙公网安备 33010602011771号