题目链接:
http://acm.hdu.edu.cn/showproblem.php?pid=4427
题目大意:
给k个正整数的和n,最小公倍数m,求一共有多少种方案。
解题思路:
很明显的dp.
状态很好想,dp[][i][j]表示当前和为i,最小公倍数为j的方案数。
对于k个数中的任何一个数,它一定是m的约数,否则最小公倍数一定不为m(一个很大的剪枝).
用滚动数组保存。对于每个数,枚举可能取的值(m的约数),最多只有32个(1000内数的因数个数最多只有32个)。
然后滚动数组初始化时注意只用初始化可能到达的位置,其他的不用初始化1000*32就够了,我弱逼的搞成1000*1000,然后一直TLE。。。。
代码:
#include <iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<cstdlib>
#include<cctype>
#include<map>
#include<vector>
#include<set>
#include<queue>
#include<string>
using namespace std;
const int INF = 0x3f3f3f3f;
const double eps = 1e-6;
const double PI = acos(-1.0);
//typedef __int64 ll;
//#pragma comment(linker, "/STACK:1024000000,1024000000")
/*
freopen("data.in","r",stdin);
freopen("data.out","w",stdout);
*/
#define Maxn 1100
#define M 1000000007
int dp[2][Maxn][Maxn],lcm[1005][1005];
int n,m,k;
int yin[1100],cnt;
int gcd(int a,int b)
{
if(a%b==0)
return b;
return gcd(b,a%b);
}
void pre(int x)
{
for(int i=1;i<=x;i++)
if(x%i==0)
yin[++cnt]=i; //初始化时,不该对称的累加上去,那样剪枝的话就不是递增的了
return ;
}
int main()
{
//printf("%lld\n",(1LL<<31) -1);
int i,j;
for(i=1;i<=1000;i++)
for(j=1;j<=1000;j++)
lcm[i][j]=i/gcd(i,j)*j;
while(~scanf("%d%d%d",&n,&m,&k))
{
cnt=0;
pre(m);
memset(dp,0,sizeof(dp));
for(int i=1;i<=cnt;i++)
dp[0][yin[i]][yin[i]]=1;
int cur=0,nex;
for(int i=2;i<=k;i++)
{
nex=cur^1;
for(int w=1;w<=n;w++) //只用初始化1000*32,弱逼的1000*1000果断超时了
for(int q=1;q<=cnt;q++)
dp[nex][w][yin[q]]=0;
for(int jj=1;jj<=cnt&&yin[yy]<=(n-(k-1));jj++)
for(int ss=1;ss<=cnt;ss++)
{
int j=yin[jj];
int s=yin[ss];
if(m%lcm[j][s]) //不是的话继续
continue;
for(int p=i-1;p<=n-(k-i)-j;p++) //前面至少为i-1,正整数
if(dp[cur][p][s])
dp[nex][j+p][lcm[s][j]]=(dp[nex][j+p][lcm[s][j]]+dp[cur][p][s])%M;
}
cur=nex;
}
printf("%d\n",dp[cur][n][m]);
}
return 0;
}