LightOJ 1245 除法分块

不羁的心 提交于 2019-11-27 12:08:36

I was trying to solve problem ‘1234 - Harmonic Number’, I wrote the following code

long long H( int n ) {
long long res = 0;
for( int i = 1; i <= n; i++ )
res = res + n / i;
return res;
}

Yes, my error was that I was using the integer divisions only. However, you are given n, you have to find H(n) as in my code.

Input
Input starts with an integer T (≤ 1000), denoting the number of test cases.
Each case starts with a line containing an integer n (1 ≤ n < 231).

Output
For each case, print the case number and H(n) calculated by the code.

Sample Input
11
1
2
3
4
5
6
7
8
9
10
2147483647

Sample Output
Case 1: 1
Case 2: 3
Case 3: 5
Case 4: 8
Case 5: 10
Case 6: 14
Case 7: 16
Case 8: 20
Case 9: 23
Case 10: 27
Case 11: 46475828386

除法分块:

除法分块就是由于一个整数 N 除以 i (从 1 - N) 总共只有 ≈ sqrt(N) 个值 (PS : sqrt(N) 就是 根号N , 这里的值只包括除法中的商 , 和余数没有半毛钱关系)所以,我们可以把计算 ∑N / i (1 <= i <= N) 的复杂度从 O(N) 降到 O(sqrt(N)) .
具体操作如下:
我们可以维护一个区间段 Begin 和 End , 一开始 Begin = 1.
每次计算,我们就先从 Begin 的值计算出这个区间的 End:
End = min( N , N / (N / Begin) ) (PS : 这里的 / 是指程序中的整除运算(下同) , 习惯化简的同学们注意了)
然后 , 这个区间的 ∑ N / i (Begin <= i <= End) 就是 (N / Begin) * (End - Begin + 1) (PS : * 指乘法)
下一个区间的 Begin = 当前区间的 End + 1 .

方法一:比较耗时
代码如下:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;
ll solve(ll n){     //除法分块
    ll l=1,r;
    ll ans=0;
    while(l<=n){
        r=n/(n/l);
        ans+=(n/l)*(r-l+1);
        l=r+1;
    }
    return ans;
}
int main(){
    int T,t=0;
    scanf("%d",&T);
    while(T--){
        ll n;
        scanf("%lld",&n);
        t++;
        printf("Case %d: %lld\n",t,solve(n));
    }
    return 0;
}

方法二:

枚举到sqrtn(n)即可,其实思路就是n/i结果为1的有n/2个,那么结果为2的数字有n/2-n/3个,结果为3的数字有n/3-n/4个…然后我们计算结果为x的时候也顺便可以计算一下i为x的情况,那么只需要进行到sqrt(n)就可以了。

代码如下:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#define ll long long
using namespace std;
int main(){
    int T,t=1;
    scanf("%d",&T);
    while(T--){
        int n,m,i;
        scanf("%d",&n);
        m=(int)sqrt(n);
        ll sum=0;
        for(i=1;i<=m;i++){
            sum+=n/i;
            sum+=i*(n/i-n/(i+1));
        }
        i--;
        if(n/i==i)sum-=i;
        printf("Case %d: %lld\n",t++,sum);
    }
    return 0;
}
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!