HDU 4427 Math Magic

d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k] 表示的是 前 i i i个数,总和为 j j j ,LCM 为 k k k 的时候的种类数。

如果现在已知 d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k],那么我们可以求得下一个状态:

设下一个数为 s s s ,那么 d p [ i + 1 ] [ j + s ] [ l c m [ k , s ] ] dp[i+1][j+s][lcm[k,s]] dp[i+1][j+s][lcm[k,s]] 状态可以由 d p [ i + 1 ] [ j + s ] [ l c m [ k , s ] ] dp[i+1][j+s][lcm[k,s]] dp[i+1][j+s][lcm[k,s]] 转移过来,故 d p [ i + 1 ] [ j + s ] [ l c m [ k , s ] ] dp[i+1][j+s][lcm[k,s]] dp[i+1][j+s][lcm[k,s]] 加上 d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k] 的值即可。

那么此题需要空间优化以及时间剪枝优化。

由于操作的是三维数组上下两层,故可以用滚动数组优化空间复杂度。

再一点的是:
对于 d p [ i + 1 ] [ j + s ] [ l c m [ k , s ] ] dp[i+1][j+s][lcm[k,s]] dp[i+1][j+s][lcm[k,s]] 。这个是由 d p [ i + 1 ] [ j + s ] [ l c m [ k , s ] ] dp[i+1][j+s][lcm[k,s]] dp[i+1][j+s][lcm[k,s]] 转移过来的,那么一定要满足以下条件:

1 、 j + s &lt; = n ( n 为 总 和 ) 1、j + s &lt;= n (n 为总和) 1j+s<=nn

2 、 l c m [ k , s ] &lt; = m ( m 为 所 有 数 的 l c m ) 2、lcm[k,s] &lt;= m(m为 所有数的lcm) 2lcm[k,s]<=mmlcm

对于上面第二条我们得知: k k k s s s 一定是 m m m 的约数,因为它们的最小公倍数是 m m m

故预处理出 m m m 的所有约数,然后枚举 k 和 s k 和 s ks 的时候,时间复杂度就会少许多。

然后就是还要预处理一下,在1000范围内的数,所有数的最小公倍数,因为之后 d p dp dp 会用到。

代码如下:

#include<iostream>
#include<algorithm>
#include<string.h>
using namespace std;
int lcm[1008][1008];
int dp[2][1008][1008];
int fac[1008];
int n, m, k, cnt;
const int mod = (int)(1e9 + 7);
int gcd(int a, int b)
{
	if (b)
		return gcd(b, a%b);
	return a;
}
int main()
{
	for (int i = 1; i <= 1000; i++) {
		for (int j = i; j <= 1000; j++) {
			lcm[i][j] = lcm[j][i] = (i * j) / gcd(i, j);
		}
	}
	while (~scanf("%d%d%d", &n, &m, &k))
	{
		cnt = 0;
		for (int i = 1; i <= m; i++) {
			if (m%i == 0) fac[++cnt] = i;
		}
		memset(dp, 0, sizeof(dp));
		for (int i = 1; i <= cnt; i++) {
			dp[0][fac[i]][fac[i]] = 1;
		}
		int g = 0, t, tt, l;
		for (int i = 1; i < k; i++) {
			memset(dp[g ^ 1], 0, sizeof(dp[g ^ 1]));
			for (int j = i; j < n; j++) {
				for (int w = 1; w <= cnt; w++) {
					 t = fac[w];
					if (j + t > n) break;
					for (int q = 1; q <= cnt; q++) {
						 tt = fac[q];
						 if (!dp[g][j][tt]) continue;
						 l = lcm[t][tt];
						 if (l > m) continue;
						 dp[g ^ 1][j + t][l] += dp[g][j][tt];
						 dp[g ^ 1][j+t][l] %= mod;
					}
				}
				/*for (int w = 1; w <= cnt; w++) {
					 int t = fac[w];
					 if (!dp[g][j][t]) continue;
					 for (int q = 1; q <= cnt; q++) {
						 int tt = j + fac[q];
						 if (tt > n) break;
						int l = lcm[t][fac[q]];
						 dp[g ^ 1][tt][l] += dp[g][j][t];
						 dp[g ^ 1][tt][l] %= mod;
					}
				}*/
			}
			g ^= 1;
		}
		printf("%d\n", dp[g][n][m]);
	}
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值