P3746 [六省联考2017]组合数问题 矩阵乘法

题意:

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