HDU 5656 CA Loves GCD

题意:
问题描述

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

思路:比赛的时候没做出来….(我真是太弱了
根据官方题解做的ORZ

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<stdio.h>
#include<string.h>
#include<iostream>
#include<algorithm>
#include<math.h>
#include<queue>
#include<stack>
#include<string>
#include<vector>
#include<map>
#include<set>
using namespace std;
#define mem(a,b) memset(a,b,sizeof(a))
#define lowbit(x) (x&(-x))
typedef long long LL;
#define maxn 1005
#define mod 100000007
const int inf=(1<<28)-1;
int G[maxn][maxn];
int Gcd(int a,int b)
{
    if(G[a][b]) return G[a][b];
    if(!a) return b;
    return Gcd(b%a,a);
}
void get_gcd()
{
    for(int i=1;i<maxn;++i)
     for(int j=1;j<maxn;++j)
     G[i][j]=Gcd(i,j);
}
int A[maxn];
LL dp[maxn][maxn];
int main()
{
    get_gcd();
    int T;
    scanf("%d",&T);
    while(T--)
    {
        int n,maxs=0;
        scanf("%d",&n);
        for(int i=1;i<=n;++i)
        {
            scanf("%d",&A[i]);
            maxs=max(maxs,A[i]);
        }
        mem(dp,0);
        for(int i=1;i<=n;++i)
        {
            dp[i][A[i]]=1;
            for(int j=1;j<=maxs;++j)
            if(dp[i-1][j])
            {
                dp[i][j]=(dp[i][j]+dp[i-1][j])%mod;
                dp[i][G[j][A[i]]]=(dp[i][G[j][A[i]]]+dp[i-1][j])%mod;
            }
        }
        LL ans=0;
        for(int i=1;i<=maxs;++i)
        {
            ans=(ans+i*dp[n][i])%mod;
        }
        printf("%lld\n",ans);
    }
    return 0;
}

思路二:
这里写图片描述

#include<stdio.h>
#include<string.h>
#include<iostream>
#include<algorithm>
#include<math.h>
#include<queue>
#include<stack>
#include<string>
#include<vector>
#include<map>
#include<set>
using namespace std;
#define mem(a,b) memset(a,b,sizeof(a))
#define lowbit(x) (x&(-x))
typedef long long LL;
#define maxn 1005
const LL mod=100000007;
const int inf=(1<<28)-1;
LL dp[maxn];
int A[maxn],cal[maxn];
LL quick_pow(LL a,LL b,LL m)
{
    LL res=1,tmp=a;
    while(b)
    {
        if(b&1) res=(res*tmp)%m;
        tmp=(tmp*tmp)%m;
        b/=2;
    }
    return res;
}
LL max(LL a,LL b)
{
    if(a>b) return a;
    return b;
}
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        mem(cal,0);
        int n;
        int maxs=0;
        scanf("%d",&n);
        for(int i=1;i<=n;++i)
        {
            scanf("%d",&A[i]);
            cal[A[i]]++;
            maxs=max(A[i],maxs);
        }
        for(int i=1;i<=maxs;++i)
        {
            int t=i;
            while(t+i<=maxs)
            {
                t+=i;
                cal[i]+=cal[t];
            }
        }
        LL ans=0;
        for(int i=maxs;i>=1;--i)
        {
            dp[i]=(quick_pow(2,cal[i],mod)-1+mod)%mod;
            int t=i;
            while(t+i<=maxs)
            {
                t+=i;
                dp[i]=(dp[i]-dp[t]+mod)%mod;
            }
            ans=(ans+dp[i]*i)%mod;
        }
        printf("%lld\n",ans);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值