hdu5656 CA Loves GCD

来源:互联网 发布:快手后期制作软件名字 编辑:程序博客网 时间:2024/06/02 07:01

题目的意思就是:
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之间,我们计算每个数的情况就可以了!
这也在提示我们,以后刷题,多换个方向思考!
只需要换个角度考虑问题就行了!

1 0