bjfu1211 推公式,筛素数
题目是求fun(n)的值
fun(n)= Gcd(3)+Gcd(4)+…+Gcd(i)+…+Gcd(n).
Gcd(n)=gcd(C[n][1],C[n][2],……,C[n][n-1])
C[n][k] means the number of way to choose k things from n things.
n最大一百万,马上反映到可能是递推打表。
首先肯定是推公式了,fun(n)其实就是Gcd(n)的一个前n项和,没意义,直接看Gcd(n),把前几项列出来,发现公式是Gcd(n) = lcm(1,……,n-1,n)/lcm(1,……,n-1),其中lcm是求若干个数的最小公倍数。例如Gcd(1)=lcm(1)=1, Gcd(2)=lcm(1,2)/lcm(1)=2,
Gcd(6)=lcm(1,2,3,4,5,6)/lcm(1,2,3,4,5)=60/60=1
于是我马上想到了方法,先筛法打出一百万以内的素数表,然后从lcm(1,2)开始逐个往后递推lcm(1,……,n-1,n),开一个数组来表示其值(数组每一位表示某个素数,值表示这个素因子的个数)。但是这样的话,递推的时候必须遍历素数表,就算只考虑1000以内的素数,也超过100个,很有可能超时。
不过因为是学弟出的题,本着测试的态度,我按这个思路打了一个,果然超时,看来数据不算太水。
/* * Author : ben */ #include <cstdio> #include <cstdlib> #include <cstring> #include <cmath> #include <ctime> #include <iostream> #include <algorithm> #include <queue> #include <set> #include <map> #include <stack> #include <string> #include <vector> #include <deque> #include <list> #include <functional> #include <numeric> #include <cctype> using namespace std; #ifdef ON_LOCAL_DEBUG #else #endif typedef long long LL; const int MAXN = 1000009; LL ans[MAXN]; int pn, pt[MAXN], num[MAXN]; bool ip[MAXN]; int N = 1000000; void init_prime_table() { memset(ip, true, sizeof(ip)); int i; ip[1] = false; pn = 0; pt[pn++] = 2; for (i = 4; i < N; i += 2) { ip[i] = false; } for (i = 3; i * i <= N; i += 2) { if (!ip[i]) continue; pt[pn++] = i; for (int s = 2 * i, j = i * i; j < N; j += s) ip[j] = false; } for (; i < N; i++) { if (ip[i]) { pt[pn++] = i; } } } inline int mypow(int a, int b) { int ret = 1; while (b--) { ret *= a; } return ret; } int comput() { ans[1] = 1; for (int i = 2; i <= N; i++) { if (ip[i]) { num[i]++; ans[i] = i; continue; } int _gcd = 1; int n = i; for (int j = 0; j < pn; j++) { if (n == 1) { break; } if (n > 1000 && ip[n]) { break; } int t = 0; while (n % pt[j] == 0) { t++; n = n / pt[j]; } if (t > num[pt[j]]) { _gcd *= mypow(pt[j], t - num[pt[j]]); num[pt[j]] = t; } } ans[i] = _gcd; } for (int i = 4; i < MAXN; i++) { ans[i] += ans[i - 1]; } return 0; } int main() { #ifdef ON_LOCAL_DEBUG freopen("data.in", "r", stdin); #endif int n; // get_prime_table(MAXN); init_prime_table(); // pn = pt.size(); memset(num, 0, sizeof(num)); comput(); while (scanf("%d", &n) == 1) { printf("%I64d\n", ans[n]); } return 0; }
下面是正解。正解的方法对上面那种方法的改进类似于筛法对普通求素数法的改进。原来的方法这么工作:比如在递推到n=8的时候,发现8有三个素因子2,而之前的lcm里只有两个素因子2,所以这次的值会再乘一个2。那么新方法直接倒过来,当递推到2的时候,发现2是素数,那么就把2^2,2^3,2^4等等的ans里都预先乘上一个2。这样就避免了遍历素数表,降低了复杂度,而且代码更为简洁。代码如下:
/* * Author : ben */ #include <cstdio> #include <cstdlib> #include <cstring> #include <cmath> #include <ctime> #include <iostream> #include <algorithm> #include <queue> #include <set> #include <map> #include <stack> #include <string> #include <vector> #include <deque> #include <list> #include <functional> #include <numeric> #include <cctype> using namespace std; #ifdef ON_LOCAL_DEBUG #else #endif typedef long long LL; const int MAXN = 1000009; LL ans[MAXN]; int pn, pt[MAXN]; bool ip[MAXN]; int N = MAXN; void init_prime_table() { memset(ip, true, sizeof(ip)); int i; ip[1] = false; pn = 0; pt[pn++] = 2; for (i = 4; i < N; i += 2) { ip[i] = false; } for (i = 3; i * i <= N; i += 2) { if (!ip[i]) continue; pt[pn++] = i; for (int s = 2 * i, j = i * i; j < N; j += s) ip[j] = false; } for (; i < N; i++) { if (ip[i]) { pt[pn++] = i; } } } int comput() { fill(ans, ans + N + 1, 1); for (int i = 2; i <= N; i++) { if (ip[i]) { LL t = i; while (t <= N) { ans[t] *= i; t *= i; } } } for (int i = 4; i < MAXN; i++) { ans[i] += ans[i - 1]; } return 0; } int main() { #ifdef ON_LOCAL_DEBUG // freopen("test.in", "r", stdin); // freopen("data.out", "w", stdout); freopen("data.in", "r", stdin); #endif int n; init_prime_table(); comput(); while (scanf("%d", &n) == 1) { printf("%I64d\n", ans[n]); } return 0; }