hdu 4427 Math Magic

本文解析了一道经典的DP题目——HDU 4427,介绍了如何利用DP解决特定序列计数问题的方法。通过优化状态转移方程及存储策略,实现了高效求解。

题目: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;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值