HDU 4427 Math Magic (2012长春-dp )

101 篇文章 1 订阅

题意:

告诉你n,m,k。

让你构造k 个数, 使得他们的和为n , 最小公倍数是m, 求方案数?

思路:

本以为是一个数学, 是一个水dp。


我们直接按照他描述的建立状态。

令 dp[i][j][k] , 表示 目前选了i 个数, 和为j , 最小公倍数是k 的方案数。

那么转移方程就很好写了   dp[i+1][j + a][lcm(a, k)] += dp[i][j][k].

但是得优化一些地方。

1.首先空间上  , 这样是爆内存的,  把第一维改成滚动数组就行了。

第二个 , 时间上优化。

转移时候你得枚举上一个数是啥, 这一个数是啥 , 这样1000的方了。

我们知道 ,假如k 个数的最小公倍数是m 的话 , 那么这k个数都是m 的因子。

2.那这样就好办了。 我们先处理m 的因子数量(最多不超过50).

然后枚举因子进行转移就好了。  这就成了50的方了。

3.另外还可以加入一些小的优化。

因为转移到m 只需要因子,  那么不如把因子离散化一下。

这样内存就开到了 dp[2][1000][50].

4. 在一点就是 lcm 时候也比较慢, 不如预处理出来所有因子的lcm。 这样olog 变成了 o1

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

int n, m, k;

const int mod = 1000000000 + 7;

long long dp[2][1007][50];


int gcd(int a,int b){
    return !b ? a :gcd(b, a%b);
}

int lcm(int a,int b){
    return a*b / gcd(a,b);
}

int LCM[1007][1007];

int fac[1007];
int cnt = 0;

void add(long long& ans, long long x){
    ans += x;
    if (ans >= mod) ans -= mod;

}
int pos[1007];
int main(){

    while(~scanf("%d %d %d",&n, &m, &k)){
        for (int i = 0; i <= n; ++i){
            for (int j = 0; j < 50; ++j){
                dp[0][i][j] = 0;
            }
        }


        cnt = 0;
        for (int i = 1; i <= m; ++i){
            if (m % i == 0) {
                fac[cnt++] = i;
                pos[i] = cnt-1;
                dp[0][i][pos[i] ] = 1;
            }
        }


        for (int i = 0; i < cnt; ++i){
            for (int j = 0; j < cnt; ++j){
                LCM[i][j] = lcm(fac[i], fac[j]);
            }
        }


        for (int i = 1; i < k; ++i){
            int second = (i & 1);
            int first = second ^ 1;

            for (int j = 0; j <= n; ++j){
                for (int o = 0; o < 50; ++o){
                    dp[second][j][o] = 0LL;
                }
            }

            for (int j = 0; j <= n; ++j){
                for (int l = 0; l < cnt; ++l){
                    int f = fac[l];
                    if (dp[first][j][pos[f] ]){
                        for (int  m = 0; m < cnt; ++m){
                            int f2 = fac[m];
                            if (j + f2 <= n)add(dp[second][j + f2][pos[LCM[l][m]] ] , dp[first][j][pos[f] ]);
                        }
                    }
                }
            }
        }
        printf("%I64d\n", dp[!(k & 1) ][n][pos[m] ]);
    }
    return 0;
}


Math Magic

Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 2133    Accepted Submission(s): 709


Problem Description
Yesterday, my teacher taught us about math: +, -, *, /, GCD, LCM... As you know, LCM (Least common multiple) of two positive numbers can be solved easily because of a * b = GCD (a, b) * LCM (a, b).
In class, I raised a new idea: “how to calculate the LCM of K numbers”. It's also an easy problem indeed, which only cost me 1 minute to solve it. I raised my hand and told teacher about my outstanding algorithm. Teacher just smiled and smiled...
After class, my teacher gave me a new problem and he wanted me solve it in 1 minute, too.
If we know three parameters N, M, K, and two equations:
1. SUM (A 1, A 2, ..., A i, A i+1,..., A K) = N
2. LCM (A 1, A 2, ..., A i, A i+1,..., A K) = M
Can you calculate how many kinds of solutions are there for A i (A i are all positive numbers).
I began to roll cold sweat but teacher just smiled and smiled. 
Can you solve this problem in 1 minute?
 

Input
There are multiple test cases.
Each test case contains three integers N, M, K. (1 <= N, M <= 1,000, 1 <= K <= 100)
 

Output
For each test case, output an integer indicating the number of solution modulo 1,000,000,007(10 9 + 7).
You can get more details in the sample and hint below.
 

Sample Input
  
  
4 2 2 3 2 2
 

Sample Output
  
  
1 2
Hint
The first test case: the only solution is (2, 2). The second test case: the solution are (1, 2) and (2, 1).
 

Source
 

Recommend
zhuyuanchen520   |   We have carefully selected several similar problems for you:   6032  6031  6030  6029  6028 
 

Statistic |  Submit |  Discuss |  Note

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值