[BZOJ1968][AHOI2005]COMMON 约数研究(数论)

题目描述

传送门

题解

题意其实就是求 ni=1d(i) d(i) 表示i的约数个数
有好几种做法。
现在对学长讲课的内容有点模糊了,隐约还记得用根n的时间蹦蹦蹦。但是一看题n的复杂度就很科学啊,然后打了一个暴力的欧拉筛A掉了。
竟然有0MS然而我300+好伤心啊。。。
然后看了看学长的代码,还有一个时间也是n的:
可以换一个角度思考,把问题转化为i有多少个倍数在1-n范围内,显然答案为 ni ,所以最终答案为 ni=1ni
MD智障,我为什么没有想到。。。
第三种做法比较神,想了很久才明白:
从上一种做法的思路延伸下去想,如果把所有的 ni 都列出来形成一个数列,你会发现有一些区间内的数都是一样的,而如果你把所有的 nni 求出来的话,就得到了一个与上一个序列“相反”的序列(其实求这个序列的目的只是为了方便代码实现)。
然后每一次只需要求一下区间和就可以了,时间复杂度降低到O( N )。

代码

欧拉筛

#include
#include
#include
using namespace std;
#define LL long long

const int max_n=1e6+5;

LL n,ans;
LL p[max_n],prime[max_n],d[max_n],t[max_n];

inline void get_d(){
    d[1]=1; t[1]=1;
    for (int i=2;i<=n;++i){
        if (!p[i]){
            prime[++prime[0]]=i;
            d[i]=2; t[i]=1;
        }
        for (int j=1;j<=prime[0]&&i*prime[j]<=n;++j){
            p[i*prime[j]]=1;
            if (i%prime[j]==0){
                t[i*prime[j]]=t[i]+1;
                d[i*prime[j]]=d[i]/(t[i]+1)*(t[i*prime[j]]+1);
                break;
            }
            else{
                d[i*prime[j]]=d[i]*2;
                t[i*prime[j]]=1;
            }
        }
    }
}

int main(){
    scanf("%lld",&n);
    get_d();
    for (int i=1;i<=n;++i)
      ans+=d[i];
    printf("%lld\n",ans);
}

O(N)

#include
#include
#include
using namespace std;
#define LL long long

LL n,ans;

int main(){
    scanf("%lld",&n);
    for (LL i=1;i<=n;++i)
      ans+=(n/i);
    printf("%lld\n",ans);
}

O( N

#include
#include
#include
using namespace std;
#define LL long long

LL n,ans;

int main(){
    scanf("%lld",&n);
    LL i,j;
    for (i=1,j=0;i<=n;i=j+1){
        j=n/(n/i);
        ans+=(j-i+1)*(n/i);
    }
    printf("%lld\n",ans);
}

总结

①数论这个东西好好感受下。
②在这里随便提一嘴今天的考试:时间复杂度别再算错了;能写的暴力一定写(今天因为算错时间复杂度以为一定过不了就没写暴力);有问题的代码也交;能打表的打表。没办法自己太煞笔。

你可能感兴趣的:(题解,省选,数论)