给定 N , M N,M N,M,求不同的序列数使得序列所有数之和为 M M M,且两两在除以 M M M后余数互不相同。
N ≤ 1 0 18 N\le 10^{18} N≤1018, M ≤ 100 M\le 100 M≤100.
题解
暴力可以考虑把 m o d M \mod M modM的取值状压,设 f i , j f_{i,j} fi,j表示选的数和为 i i i, m o d M \mod M modM是否出现过的状态为 j j j的方案数,转移显然,最后答案要乘个数的阶乘。
但这样复杂度不能接受,由于 N N N特别大,但 M M M较小,DP时可以只算 [ 0 , m ) [0,m) [0,m)中的数,再把剩余部分用组合数计算上,
这样一来,不需要记录状态,因为这个范围内它们除以 M M M的余数本身就互不相同,只需设 f i , j f_{i,j} fi,j表示和为 i i i,选了 j j j个数的方案数,转移也显然。
至于最后怎么计算,首先需要满足 M ∣ N − i M|N-i M∣N−i才能是一种合法的答案,还需要把 N − i M \frac{N-i}{M} MN−i个 M M M分配给 j j j个数,用挡板法求得方案数为 ( N − i M + j − 1 j − 1 ) \frac{N-i}{M}+j-1\choose {j - 1} (j−1MN−i+j−1),当然也还要乘上 j ! j! j!。
代码
#include#include#includeusingnamespace std;#define md 905229641#define ll long long#define M 110
ll f[2][M][M * M], inv[M];
ll C(ll x, ll y){ll s =1;for(ll i = x; i > x - y; i--) s = s *(i % md)% md;for(int i =1; i <= y; i++) s = s * inv[i]% md;return s;}intmain(){ll n;int m , i, j, k;scanf("%lld%d",&n,&m);inv[1]=1;for(i =2; i <= m; i++) inv[i]= inv[md % i]*(md - md / i)% md;f[1][0][0]=1;for(i =0; i < m ;i++){int o = i %2;memset(f[o],0,sizeof(f[o]));for(j =0; j <= m; j++)for(k =0; k <= m * j; k++){f[o][j][k]= f[1- o][j][k];if(k >= i)(f[o][j][k]+= f[1- o][j -1][k - i])%= md;}}int o =1- m %2; ll t =1, ans =0;for(j =1; j <= m; j++){t = t * j % md;for(k =0; k <= m * j; k++)if(f[o][j][k]&&(n - k)% m ==0){ans =(ans + t * f[o][j][k]% md *C((n - k)/ m + j -1, j -1))% md;}}printf("%lld\n", ans);return0;}