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的每个数。
1≤T≤50, 1≤N≤1000, 1≤Ai≤1000输出描述
对于每组数据输出一行一个整数表示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;
}