Harmonic Number LightOJ - 1234(打表+技巧||调和级数逼近公式)

Harmonic Number

LightOJ - 1234

In mathematics, the nth harmonic number is the sum of the reciprocals of the first n natural numbers:

In this problem, you are given n, you have to find Hn.


Input

Input starts with an integer T (≤ 10000), denoting the number of test cases.

Each case starts with a line containing an integer n (1 ≤ n ≤ 108).

Output

For each case, print the case number and the nth harmonic number. Errors less than 10-8 will be ignored.

Sample Input

12

1

2

3

4

5

6

7

8

9

90000000

99999999

100000000

Sample Output

Case 1: 1

Case 2: 1.5

Case 3: 1.8333333333

Case 4: 2.0833333333

Case 5: 2.2833333333

Case 6: 2.450

Case 7: 2.5928571429

Case 8: 2.7178571429

Case 9: 2.8289682540

Case 10: 18.8925358988

Case 11: 18.9978964039

Case 12: 18.9978964139

这个公式是一个调和级数,并不收敛,但是当n趋近于一个较大的值的时候有一个近似的公式H(n) = c + ln(n)
c是欧拉常数=0.57721566490153286060651209

由于题目要求精度为10^-8,常数r也是前人推导出来的,然而也只推导了有限位数,所以正常利用这个公式,并不能达到精度要求,我们只好比较样例和我们自己输出的数据,增添一些可行的项,经尝试,在原公式改成 c+ln(n+1)
再减去一个1.0/(2*n)恰好可以满足精度,也算是投机取巧了。
code1:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
using namespace std;
const double c = 0.57721566490153286060651209;
const int maxn = 1e5+10;
double a[maxn];
void init(){
    a[1] = 1.0;
    for(int i = 2; i < maxn; i++){
        a[i] = a[i-1] + 1.0 / (double)i;
    }
}
int main(){
    int t,cas = 0;
    init();
    scanf("%d",&t);
    while(t--){
        int n;
        scanf("%d",&n);
        if(n < maxn){
            printf("Case %d: %.10f\n",++cas,a[n]);
        }
        else{
            printf("Case %d: %.10f\n",++cas,c+log((double)n+1.0)-1.0/(2.0*n));
        }
    }
    return 0;
}
但是上面的方法可以说是试出来的,并不具有普适性
如果只靠打表,而且是有技术含量的打表,也能很好的解决这个问题,既然10^8的表我们打不出来,但是200万的表我们还是能打的,这样一来,我们先平均分而且间隔着,每50个记录一个,先把分布在10^8数据中的值放在表里,真正计算时,我们便先找到距离我们的n最近的且小于n的在表中存过数据的一个数,然后再在这个数的基础上递推这往下算,这样一来,便大大降低了时间复杂度

code2:
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int maxn = 1e8;
const int N = 2100000;
double a[N];
void init(){
    a[0] = 0.0;
    int top = 1;
    double sum = 0.0;
    for(int i = 1; i <= maxn; i++){
        sum += (1.0 / (double)i);
        if(i % 50 == 0){//记录前多少个50的和
            a[top++] = sum;
        }
    }
}
int main(){
    int t,cas = 0;
    scanf("%d",&t);
    init();
    while(t--){
       int n;
       scanf("%d",&n);
       int num = n / 50;//看看离这个数最近的是前多少个50的数
       double s = a[num];
       for(int i = num*50+1; i <= n; i++){//找到了前多少个50,乘50得到这个数从它下一个开始计算
          s += (1.0 / (double)i);
       }
       printf("Case %d: %.10f\n",++cas,s);
    }
    return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值