题意:给出三个整数N,M,K 和:sum(a1...ak) =N, 最小公倍数lcm(a1,...,ak)=M, 以及K个数字,求出满足条件的组合可能 (1 ≤ N, M ≤ 1,000, 1 ≤ K ≤ 100)
(这道题比较综合,相比昨天做的dp难多了,然后就没做出来.....只能看大佬博客再自己重新来写)
思路:如果从dp状态转移的角度来设置的话 就要设置dp[k][n][m] 但是 1000*1000*100太大了,必须要优化dp的存储
我们从dp转移关系中可以得到 dp[i][p + fact[j]][lcm[fact[q]][fact[j]]] += dp[i-1][p][fact[q]](其中fact[]为因子,p q 为下标)
所以所有的转移过程中只需要两个状态 i与i-1 所以两种状态就用 ^来改变
对于因子的求法,我们先循环枚举(预处理) lcm[i][j] = lcm[j][i] ,其中lcm利用agcd(a,b)*b来得到
完整代码:
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int maxn = 1005; const int mod = 1000000007; int n, m, k; int lcm[maxn][maxn]; int dp[2][maxn][maxn];//由于dp[k][][] = dp[k-1][][],为了节省空间就使用滚动数组 int fact[maxn], cnt; 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 init() { for(int i = 1; i <=1000; i++) for(int j = 1; j<=i; j++) lcm[j][i] = lcm[i][j] = LCM(i, j); } void solve() { cnt = 0; for(int i = 1; i<=m; i++)//求出其所有的因子 if(m%i==0) fact[cnt++] = i; int now = 0; memset(dp[now], 0, sizeof(dp[now])); for(int i = 0; i<cnt; i++) dp[now][fact[i]][fact[i]] = 1; for(int i = 1; i<k; i++) { now ^= 1;//(数组下标进行滚动) for(int p=1; p<=n; p++) for(int q=0; q<cnt; q++) { dp[now][p][fact[q]] = 0; } for(int p=1; p<=n; p++) { for(int q=0; q<cnt; q++)//因子1 { if(dp[now^1][p][fact[q]]==0) continue; for(int j=0; j<cnt; j++)//因子2 { int now_sum = p + fact[j]; if(now_sum>n) continue; int now_lcm = lcm[fact[q]][fact[j]]; dp[now][p + fact[j]][lcm[fact[q]][fact[j]]] += dp[now^1][p][fact[q]];//状态转移方程 dp[now][now_sum][now_lcm] %= mod; } } } } printf("%d ",dp[now][n][m]); } int main() { init(); while(~scanf("%d%d%d", &n, &m, &k)) solve(); return 0; }