题目描述
题解
题意其实就是求
∑ni=1d(i)
,
d(i)
表示i的约数个数
有好几种做法。
现在对学长讲课的内容有点模糊了,隐约还记得用根n的时间蹦蹦蹦。但是一看题n的复杂度就很科学啊,然后打了一个暴力的欧拉筛A掉了。
竟然有0MS然而我300+好伤心啊。。。
然后看了看学长的代码,还有一个时间也是n的:
可以换一个角度思考,把问题转化为i有多少个倍数在1-n范围内,显然答案为
⌊ni⌋
,所以最终答案为
∑ni=1⌊ni⌋
。
MD智障,我为什么没有想到。。。
第三种做法比较神,想了很久才明白:
从上一种做法的思路延伸下去想,如果把所有的
⌊ni⌋
都列出来形成一个数列,你会发现有一些区间内的数都是一样的,而如果你把所有的
⌊n⌊ni⌋⌋
求出来的话,就得到了一个与上一个序列“相反”的序列(其实求这个序列的目的只是为了方便代码实现)。
然后每一次只需要求一下区间和就可以了,时间复杂度降低到O(
N−−√
)。
代码
欧拉筛
#include<iostream>
#include<cstring>
#include<cstdio>
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<iostream>
#include<cstring>
#include<cstdio>
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<iostream>
#include<cstring>
#include<cstdio>
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);
}
总结
①数论这个东西好好感受下。
②在这里随便提一嘴今天的考试:时间复杂度别再算错了;能写的暴力一定写(今天因为算错时间复杂度以为一定过不了就没写暴力);有问题的代码也交;能打表的打表。没办法自己太煞笔。