题目:http://acm.hdu.edu.cn/showproblem.php?pid=4427
题目大意:就是给你两个数 n、m,问你有多少个长度为 k 的序列,它们的和是 n,lcm是m,然后输出这个种数。
思路:很好的一道DP题。状态量很容易想到,d[ i ][ j ][ k ] 表示前 i 个和为 j,lcm为 k 的种数,然后地推下一个。空间是没有问题,只要把 i 优化一下就行,关键是时间问题,如果是直接暴力枚举下一个数,那么时间肯定爆。其实这里要想到一点,由于lcm为m,这个序列中的数肯定全都是 m 的因子。可以暴力一下,可以发现,像1000,它的因子并不多。但是这样做了,发现还是TLE,这里还有一个地方可以优化,根据前面那个,k 这一维,不必存lcm,只需要存这个值的编号,这样的话,因为每次都有清空,可以省出很多时间。
想到因子了,还是TLE 了很多次,优化编号那个,抱着试试的心态然后过了。。。= =
代码如下:
#include <cstdio>
#include <cstring>
#include <cmath>
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn=1111;
const int mod=1e9+7;
int dp[2][maxn][111];
int f[maxn][maxn];//f[i][j]表示i和j的最小公倍数
int gcd(int a,int b)
{
return b==0?a:gcd(b,a%b);
}
int lcm(int a,int b)
{
return a*b/gcd(a,b);
}
void init()
{
int i,j,k;
for(i=1;i<=1000;i++)
for(j=1;j<=1000;j++)
f[i][j]=lcm(i,j);
}
int a[1111];
int hash[1111];
int main()
{
init();
int n,m,k;
while(~scanf("%d%d%d",&n,&m,&k))
{
int t=0;
for(int i = 1;i <= m;i++)
{
if(m%i == 0)
{
a[t] = i;
hash[a[t]] = t;
t++;
}
}
int now = 0,pre = 0;
memset(dp[pre],0,sizeof(dp[pre]));
dp[pre][0][0]=1;
for(int p = 1;p <= k;p++)
{
now ^= 1;
memset(dp[now],0,sizeof(dp[now]));
for(int i = 0;i <= n;i++)
{
for(int j = 0;j < t;j++)
{
if(dp[pre][i][j] == 0) continue;
for(int l = 0;l < t;l++)
{
int s = i+a[l];
if(s > n) break;
int to = hash[f[a[j]][a[l]]];
//printf("to = %d,a = %d,s = %d\n",to,a[to],s);
dp[now][s][to] += dp[pre][i][j];
if(dp[now][s][to] >= mod)
dp[now][s][to] -= mod;
}
}
}
pre = now;
}
printf("%d\n",dp[now][n][t-1]);
}
return 0;
}