题意:有K个正整数,和为N,最小公倍数为M。求有多少种情况。
状态很好推的DP,令dp[i][j][k]表示前i个数和为j最小公倍数为k有多少种情况,显然dp[i][j+t][lcm(k,t)]+=dp[i-1][j][k]。但是难点在于开不出这么大的数组,所以要用滚动数组。此外,还要预处理1000以内的LCM和M的所有因数,因为既然这K个数的最小公倍数是M,那么每个数肯定都是M的因数。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
const int maxn = 1000 + 10;
const int mod = 1000000007;
int dp[2][maxn][maxn];
int N, M, K, t;
int lcm[maxn][maxn];
int fac[maxn];
int cnt = 0;
int GCD(int a, int b)
{
return b == 0 ? a : GCD(b, a % b);
}
int LCM(int a, int b)
{
return a / GCD(a, b) * b;
}
void getlcm()
{
for (int i = 1; i <= 1000; i++)
for (int j = 1; j <= 1000; j++)
lcm[i][j] = LCM(i, j);
}
void getfac()
{
memset(fac, 0, sizeof(fac));
cnt = 0;
for (int i = 1; i <= M; i++)
if (M % i == 0) fac[cnt++] = i;
}
int main()
{
getlcm();
while (scanf("%d %d %d", &N, &M, &K) != EOF)
{
getfac();
t = 0;
memset(dp, 0, sizeof(dp));
for (int i = 0; i < cnt; i++) dp[t][fac[i]][fac[i]] = 1;
for (int i = 1; i < K; i++)
{
memset(dp[t ^ 1], 0, sizeof(dp[t ^ 1]));
for (int j = i; j < N; j++)
{
for (int p = 0; p < cnt; p++)
{
if (dp[t][j][fac[p]] == 0) continue;
for (int q = 0; q < cnt; q++)
{
if (j + fac[q] <= N)
{
dp[t ^ 1][j + fac[q]][lcm[fac[p]][fac[q]]] += dp[t][j][fac[p]];
dp[t ^ 1][j + fac[q]][lcm[fac[p]][fac[q]]] %= mod;
}
}
}
}
t ^= 1;
}
printf("%d\n", dp[t][N][M]);
}
return 0;
}