HDU 5656 CA Loves GCD (BestCoder Round #78) DP

CA Loves GCD

Time Limit: 6000/3000 MS (Java/Others) Memory Limit: 262144/262144 K (Java/Others)

问题描述

CA喜欢是一个热爱党和人民的优秀同♂志,所以他也非常喜欢GCD(请在输入法中输入GCD得到CA喜欢GCD的原因)。
现在他有N个不同的数,每次他会从中选出若干个(至少一个数),求出所有数的GCD然后放回去。
为了使自己不会无聊,CA会把每种不同的选法都选一遍,CA想知道他得到的所有GCD的和是多少。
我们认为两种选法不同,当且仅当有一个数在其中一种选法中被选中了,而在另外一种选法中没有被选中。

输入描述

第一行 T,表示有 T组数据。
接下来 T 组数据,每组数据第一行一个整数 N,表示CA的数的个数,接下来一行 N 个整数 Ai
​​ 表示CA的每个数。
1T50, 1N1000, 1Ai1000

输出描述

对于每组数据输出一行一个整数表示CA所有的选法的GCD的和对 100000007 取模的结果。

输入样例

2
2
2 4
3
1 2 3

输出样例

8
10


解题思路

官方题解

By YJQ
我们令dp[i][j]表示在前i个数中,选出若干个数使得它们的gcd为j的方案数,于是只需要枚举第i+1个数是否被选中来转移就可以了

令第i+1个数为v,当考虑 dp[i][j] 的时候,我们令

dp[i+1][j]+=dp[i][j],dp[i+1][gcd(j,v)]+=dp[i][j]

复杂度O(N*MaxV),MaxV 为出现过的数的最大值

其实有O(MaxV*log(MaxV))的做法,我们考虑记f[i]表示从这些数中选择若干个数,使得他们的gcd是i的倍数的方案数。假如有K个数是i的倍数,则f[i]=2^K-1,再用g[i]表示从这些数中选择若干个数,使得他们的gcd是i的方案数,则g[i]=f[i] - g[j] (对于所有j是i的倍数)。

由调和级数可以得到复杂度为O(MaxV *log(MaxV))

两种方法,我按第一种写了一下代码,挂到博客上回顾用。

代码

#include <cstdio>
#include <cstring>
using namespace std;
int dp[1001][1001], buf[1001][1001];
int gcd(int a, int b) {  //记忆化gcd
    if (buf[a][b])return buf[a][b];
    if (b == 0)return buf[a][b] = a;
    return buf[a][b] = buf[b][a % b] = gcd(b, a % b);
}
int main() {
    int tt, n, maxnum = 0, t;
    int a[1001];
    scanf("%d", &tt);
    while (tt--) {
        maxnum = 0;
        scanf("%d", &n);
        memset(dp, 0, sizeof(int) * 1001 * 1001);
        for (int i = 1; i <= n; i++) {
            scanf("%d", &a[i]);
            if (a[i] > maxnum)maxnum = a[i];
        }
        for (int i = 1; i <= n; i++) {
            dp[i][a[i]] = 1;
            for (int j = maxnum; j >= 1; --j) {
                dp[i][j] = (dp[i][j] + dp[i - 1][j]) % 100000007;
                if (dp[i - 1][j])  //这里可以节省很多时间
                    dp[i][gcd(a[i],j)] = (dp[i][gcd(a[i],j)] + dp[i - 1][j]) % 100000007;
            }
        }
        long long ans = 0;
        for (int i = 1; i <= maxnum; ++i) {
            ans = (ans + (long long)dp[n][i] * (long long)i) % 100000007;
        }
        printf("%I64d\n", ans);
    }
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值