hdu5656 CA Loves GCD

题目的意思就是:
n个数,求n个数所有子集的最大公约数之和。

第一种方法:
枚举子集,求每一种子集的gcd之和,n=1000,复杂度O(2^n)。
谁去用?

所以只能优化!
题目中有很重要的一句话!

We guarantee that all numbers in the test are in the range [1,1000].

这句话对解题有什么帮助?
子集的种数有2^n种,但是,无论有多少种子集,它们的最大公约数一定在1-1000之间。
所以,我们只需要统计1-1000的最大公约数中可以有多少中方法凑成就可以了!

我们就会考虑dp。
官方题解:

我们令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 为出现过的数的最大值。

看代码:

#include<cstdio>
#include<cstring>

const int mod=1e8+7;
const int maxn=1005;

typedef long long ll;
int a[maxn];
ll dp[maxn][maxn];
int g[maxn][maxn];

int gcd__(int n,int m){
    if(m==0)return n;
    return gcd__(m,n%m);
}
void scanf_(int&data){
    data=0;
    char ch;
    while((ch=getchar())&&(ch<'0'||ch>'9')){

    }
    do{
        data=data*10+ch-'0';
        ch=getchar();
    }while(ch>='0'&&ch<='9');
}
int main(){

    int T;
    for(int i=1;i<=1000;++i){
        for(int j=i;j<=1000;++j){
            g[j][i]=g[i][j]=gcd__(i,j);
        }
    }
    scanf_(T);
    while(T--){
        int n;
        scanf_(n);
        for(int i=0;i<n;++i){
            scanf_(a[i]);
        }
        memset(dp,0,sizeof(dp));

        for(int i=0;i<n;++i){
            dp[i][a[i]]++;
            for(int j=1;j<=1000;j++){
                dp[i+1][j]+=dp[i][j];
                int v_=a[i+1];
                dp[i+1][g[v_][j]]+=dp[i][j];
                if(dp[i+1][j]>=mod)dp[i+1][j]%=mod;
                if(dp[i+1][g[v_][j]]>=mod)dp[i+1][g[v_][j]]%=mod;
            }
        }
        ll ans=0;
        for(int i=1;i<=1000;++i){
            if(!dp[n][i])continue;
            ans+=((i*dp[n][i])%mod);
            if(ans>=mod)ans%=mod;
        }
        printf("%lld\n",ans);
    }
    return 0;
}

这里的dp数组是二维的,我们优化为一维。

#include<cstdio>
#include<cstring>

const int mod=1e8+7;
const int maxn=1005;

typedef long long ll;
int a[maxn];
ll dp[maxn];
int g[maxn][maxn];

int gcd__(int n,int m){
    if(m==0)return n;
    return gcd__(m,n%m);
}
void scanf_(int&data){
    data=0;
    char ch;
    while((ch=getchar())&&(ch<'0'||ch>'9')){

    }
    do{
        data=data*10+ch-'0';
        ch=getchar();
    }while(ch>='0'&&ch<='9');
}
int main(){

    int T;
    for(int i=1;i<=1000;i++){
        for(int j=i;j<=1000;j++){
            g[j][i]=g[i][j]=gcd__(i,j);
        }
    }
    scanf_(T);
    while(T--){
        int n;
        scanf_(n);
        for(int i=0;i<n;i++){
            scanf_(a[i]);
        }
        memset(dp,0,sizeof(dp));
        for(int i=0;i<n;i++){
            for(int j=1;j<=1000;j++){
                if(!dp[j])continue;
                int _t=g[a[i]][j];
                dp[_t]=(dp[_t]+dp[j]);
                if(dp[_t]>=mod)dp[_t]%=mod;
            }
            dp[a[i]]++;
        }
        ll ans=0;
        for(int i=1;i<=1000;i++){
            if(!dp[i])continue;
            ans+=((i*dp[i])%mod);
            if(ans>=mod)ans%=mod;
        }
        printf("%lld\n",ans);
    }
    return 0;
}

感觉自己dp还是没怎么接触过,所以十分陌生!
主要还是自己的思维不够,不能转化,种数虽然很大,但是gcd的情况是不会变的,总是在1-1000之间,我们计算每个数的情况就可以了!
这也在提示我们,以后刷题,多换个方向思考!
只需要换个角度考虑问题就行了!

你可能感兴趣的:(hdu5656 CA Loves GCD)