Loading

题解 lg3746 组合数问题

题面

式子看起来很吓人哈

不过我们来考虑它的实际意义

就是在 nk 个物品中选择 模k余数为r 的方案数和

\(f[i,j]\) 表示选 在 i 个物品中选择 模k余数为j 的方案数和

容易得到 \(f[i,j]=f[i-1,j-1]+f[i-1,j]\)

当然,为了避免负数,我们写成这种模样 \(f[i,j]=f[i-1,(j+k-1)\%k]+f[i-1,j]\)

这种形式可以写成矩阵

然后就矩阵快速幂 啦啦啦啦


当然,我们可以继续优化

发现这个式子也可以不由 i-1转移过来,而是由两个状态合并而来

\(f[x+y,k]=f[x,0]f[y,k]+f[x,1]f[y,k-1]...f[x,k]f[y,0]\)

然后可以dp快速幂了


然后说如果p特殊,可以用单位根反演?(%%%boshi)


就只上第一种做法的代码吧(准确来说我只会这一种)

注意当k=1时,矩阵会退化为单个变量,用赋值的方法会挂;应该自加

#include<bits/stdc++.h>
using namespace std;
#define int long long
int const MAXN=60;
int n,p,k,r;
struct Mat{
	int m[MAXN][MAXN];
	Mat(){
		memset(m,0,sizeof(m));
	}
	Mat operator*(const Mat& b) const{
		Mat x;
		for(int i=0;i<k;i++){
			for(int l=0;l<k;l++){
				int a=m[i][l];if(!a)continue;
				for(int j=0;j<k;j++){
					x.m[i][j]+=1ll*a*b.m[l][j]%p;
					if(x.m[i][j]>=p)x.m[i][j]-=p;
				}
			}
		}
		return x;
	}
	void init(){
		for(int i=0;i<k;i++)m[i][i]=1;
	}
	void print(){
		for(int i=0;i<k;i++){
			for(int j=0;j<k;j++){
				printf("%d ",m[i][j]);
			}
			printf("\n");
		}
	}
}a,b;
void mpow(Mat &x,int b){
	Mat ans;ans.init();
	while(b){
		if(b&1)ans=x*ans;
		x=x*x;
		b>>=1;
	}
	x=ans;
}
signed main(){
	scanf("%lld%lld%lld%lld",&n,&p,&k,&r);
	a.m[0][0]=1;
	for(int i=0;i<k;i++){
		b.m[i][i]++;
		b.m[i][(i+k-1)%k]++;
	}
	mpow(b,n*k);
	a=b*a;
	printf("%lld\n",a.m[r][0]); 
	return 0;
}
posted @ 2021-02-21 12:18  fpjo  阅读(69)  评论(0编辑  收藏  举报