嘿嘿,后天就期中考试了,不过鸭梨不算大,复习的也差不多了,再加上根本没心思看,话说现在对学校的功课真的是越来越厌烦了。。。今天还不错,刷了2个题,还都是现场赛的题。。。
这题其实早就像做了,就是今年东北赛区的H题(幸亏没有去,要不然肯定是铁)下午的时候在zoj上做了模拟赛,过掉K题时,我就开始想这题。。后来怎么也没有想出来,不过猜测这是个DP。。后来听学长说,果断是。。看了@zz_1215的代码,读懂了状态方程,很简单,方法如下:
f[i][j][k]表示选i个数,总和为j,最小公倍数为k。。由于题目要求选K0个数最小公倍数为M,所以这其中任意数的最小公倍数一定是M的约数。。这样就可以写出转移方程,看代码。。。这个题有意思的是,要从后往前推,这也就是话说中的记忆化搜索吧。。。写完后发现TLE...杯具。。怎么办?两个优化,一个是K一定为M的约数,压缩一下状态。。最重要的是要预处理,实现计算和各个约数之间的最小公倍数。。。(话说本菜总没有这个意识。。。)果断AC,附代码:
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#define read(a,b,c) scanf("%d%d%d",&a,&b,&c)
#define cl(a) memset(a,0,sizeof(a))
#define mod 1000000007
#define M 1005
#define D 50
#define N 104
using namespace std;
int f[N][M][D],num[D],hash[M],m,cnt,g[D][D];
void div()
{
int i;
cl(num);cl(hash);
for (i=1;i<=m;i++)
if (m%i==0)
{
++cnt;
hash[i]=cnt;
num[cnt]=i;
}
}
int gcm(int p,int q)
{
if (p<q) return gcm(q,p);
if (p%q==0) return q;
else return gcm(q,p%q);
}
int lcm(int p,int q)
{
return p/gcm(p,q)*q;
}
void init()
{
int i,j,x,y;
cl(f);
for (i=1;i<=cnt;i++)
{
x=num[i];
f[1][x][hash[x]]=1;
for (j=1;j<=cnt;j++)
{
y=num[j];
g[i][j]=lcm(x,y);
}
}
}
int main()
{
int n,k0,i,j,k,r;
while (read(n,m,k0)!=EOF)
{
cnt=0;
div();
init();
for (i=1;i<k0;i++)
for (j=i;j<n;j++)
for (k=1;k<=cnt&&j+num[k]<=n;k++)
for (r=1;r<=cnt;r++)
if (f[i][j][r]>0)
{
int x,t;
x=num[k];
t=hash[g[r][k]];
f[i+1][j+x][t]=(f[i+1][j+x][t]+f[i][j][r])%mod;
}
printf("%d\n",f[k0][n][hash[m]]);
}
return 0;
}