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));
Problem Description2 2 2 4 3 1 2 3
8 10
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #define LL long long using namespace std; LL dp[1010][1010];//dp[i][j]表示在前i个数 //中,选出若干个数使得它们的gcd为j的方案数 int A[1010]; int gcd[1010][1010]; const LL MOD=1e8+7; int __gcd(int x,int y) { return (x!=0)?__gcd(y%x,x):y; } int main() { int n,t; cin>>t; for(int i=1;i<=1000;i++) { for(int j=1;j<=1000;j++) { gcd[i][j]=__gcd(i,j); } } while(t--) { cin>>n; memset(dp,0,sizeof(dp)); for(int i=0;i<n;i++) { scanf("%d",&A[i]); } sort(A,A+n);//可以确保当前数为所遍历的最大值; for(int i=0;i<n;i++) { for(int j=1;j<=A[i];j++) { (dp[i+1][j]+=dp[i][j])%=MOD; (dp[i+1][gcd[j][A[i]]]+=dp[i][j])%=MOD; } dp[i+1][A[i]]++; } LL ans=0; for(int i=1;i<=1000;i++) (ans+=dp[n][i]*i)%=MOD; printf("%I64d\n",ans); } return 0; }O(MaxV *log(MaxV))的做法:
#include <cstdio> #include <algorithm> #include <cstring> #include <iostream> #include <cmath> #include <queue> #include <stack> #define LL long long using namespace std; LL dp[1005]; LL cnt[1005]; const LL MOD=1e8+7; LL power(LL x,LL n) { LL ans=1; while(n) { if(n&1) { (ans*=x)%=MOD; } n>>=1; (x*=x)%=MOD; } return ans; } int main() { int x; int t,n; int maxn; scanf("%d",&t); while(t--) { memset(dp,0,sizeof(dp)); memset(cnt,0,sizeof(cnt)); maxn=0; scanf("%d",&n); for(int i=0;i<n;i++) { scanf("%d",&x); maxn=max(x,maxn); cnt[x]++; } LL ans=0,c; for(int i=maxn;i>=1;i--) { c=0; for(int j=i;j<=maxn;j+=i) { c+=cnt[j]; (dp[i]=dp[i]-dp[j]+MOD)%=MOD; } (dp[i]+=(power(2,c)-1)%MOD)%=MOD; (ans+=i*dp[i])%=MOD; } printf("%I64d\n",ans); } return 0; }