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;
}
时间: 2024-08-05 19:34:36

bjfu1211 推公式,筛素数的相关文章

* SPOJ PGCD Primes in GCD Table (需要自己推线性筛函数,好题)

题目大意: 给定n,m,求有多少组(a,b) 0<a<=n , 0<b<=m , 使得gcd(a,b)= p , p是一个素数 这里本来利用枚举一个个素数,然后利用莫比乌斯反演可以很方便得到答案,但是数据量过大,完全水不过去 题目分析过程(从别人地方抄来的) ans = sigma(p, sigma(d, μ(d) * (n/pd) * (m/pd))) Let s = pd, then ans = sigma(s, sigma(p, μ(s/p) * (n/s) * (m/s))

HDU 4873 ZCC Loves Intersection(JAVA、大数、推公式)

在一个D维空间,只有整点,点的每个维度的值是0~n-1 .现每秒生成D条线段,第i条线段与第i维度的轴平行.问D条线段的相交期望. 生成线段[a1,a2]的方法(假设该线段为第i条,即与第i维度的轴平行)为,i!=j时,a1[j]=a2[j],且随机取区间[0,n-1]内的整数.然后a1[i],a2[i]在保证a1[i]<a2[i]的前提下同样随机. 由于D条线段各自跟自己维度的轴平行,我们可以转换成只求第i个维度与第j个维度的相交期望,然后乘以C(2,n)就好了 显然线段[a1,a2]和线段[

HDU 4870 Rating(概率、期望、推公式) &amp;&amp; ZOJ 3415 Zhou Yu

其实zoj 3415不是应该叫Yu Zhou吗...碰到ZOJ 3415之后用了第二个参考网址的方法去求通项,然后这次碰到4870不会搞.参考了chanme的,然后重新把周瑜跟排名都反复推导(不是推倒)四五次才上来写这份有抄袭嫌疑的题解... 这2题很类似,多校的rating相当于强化版,不过原理都一样.好像是可以用高斯消元做,但我不会.默默推公式了. 公式推导参考http://www.cnblogs.com/chanme/p/3861766.html#2993306 http://www.cn

常见模板(欧拉筛素数,最小生成树,快排,并查集,单源最短路)

欧拉筛素数: #include<cstdio> #define maxn 10000000+10 using namespace std; int n,prime[5000001],num_prime=0,m; bool if_prime[maxn]; void euler(int limit) { for(int i=2;i<=limit;i++) { if(!if_prime[i]) prime[++num_prime]=i; for(int j=1;prime[j]*i<=l

洛谷 P3383 【模板】线性筛素数

P3383 [模板]线性筛素数 题目描述 如题,给定一个范围N,你需要处理M个某数字是否为质数的询问(每个数字均在范围1-N内) 输入输出格式 输入格式: 第一行包含两个正整数N.M,分别表示查询的范围和查询的个数. 接下来M行每行包含一个不小于1且不大于N的整数,即询问概数是否为质数. 输出格式: 输出包含M行,每行为Yes或No,即依次为每一个询问的结果. 输入输出样例 输入样例#1: 100 5 2 3 4 91 97 输出样例#1: Yes Yes No No Yes 说明 时空限制:5

筛素数

整理一下筛素数的方法 我在网上了解到两种筛素数的方法 一种是  1/3n*判断  的时间复杂度 一种是的时间复杂度应该是比这个低 先说一下第一种的思路 首先:一个数如果他除以一个素数除不尽,那么他除以该素数的倍数也除不尽 所以我们可以这么考虑 如果一个数是二或三的倍数 那么它一定不是素数 于是 对于  1 2 3 4 5 6 7 8 9 10 11 12…… 那么排除2和3的倍数 剩下的是 1 5 7 11 …… 对于六个数 6*n   6*n+1   6*n+2   6*n + 3   6*n

sgu495:概率dp / 推公式

概率题..可以dp也可以推公式 抽象出来的题目大意: 有 n个小球,有放回的取m次  问 被取出来过的小球的个数的期望 dp维护两个状态 第 i 次取出的是 没有被取出来过的小球的 概率dp[i] 和取出的是已经被取出来过的小球的概率np[i]; 如果第 i-1 次取出的是已经被取出来过的小球 那么第 i 次取出没有取出来过小球的概率即为 dp[i-1]: 反之则为 dp[i-1] - 1/n(没有取出来过的小球少了一个) 所以可以得到状态转移方程 dp[i]=dp[i-1]*(dp[i-1]-

Poj2689筛素数

题目大意: 给定一个区间l,r,求这个区间内相邻的质数中最近的两个和最远的两个.区间范围是1-2^31,区间的长度最多是10^6. 思路: 刚开始对筛选法的理解不深,不知道如何筛选任意一段区间的素数,看了题解恍然大悟,原来用的筛选法总是筛选从1-n的素数,对于为何这样筛选理解不深刻.说下1-n的筛选法,就是用一个数组is_prime[1..n]标记1-n中哪个是素数哪个不是,从2(第一个素数)开始扫描,所有2的倍数都不是素数,那么下一个素数是谁?就是我们向后从is_prime[]中找到的第一个是

poj3126 筛素数+bfs

1 //Accepted 212 KB 16 ms 2 //筛素数+bfs 3 #include <cstdio> 4 #include <cstring> 5 #include <iostream> 6 #include <queue> 7 using namespace std; 8 const int inf = 100000000; 9 const int imax_n = 10005; 10 bool pri[imax_n]; 11 bool vi