BZOJ-1968 COMMON 约数研究 数论+奇怪的姿势
1968: [Ahoi2005]COMMON 约数研究
Time Limit: 1 Sec Memory Limit: 64 MB
Submit: 1513 Solved: 1154
[Submit][Status][Discuss]
Description
Input
只有一行一个整数 N(0 < N < 1000000)。
Output
只有一行输出,为整数M,即f(1)到f(N)的累加和。
Sample Input
3
Sample Output
5
HINT
Source
Day2
奇怪的姿势,不过非常神!
求1-n的所有数的约数的个数,不妨可以转化一下,题目中f【i】为i的约数个数,但在做题中不妨从1~枚举i,计算后的f【i】表示1~n中约数包含i的数的个数,即约数i对答案的贡献。
然后,思考快速的方法去实现上述要求,不妨先小规模打表找找规律(于是打出N=14的表):
于是我们发现了一个规律,所有对答案贡献相同的i,是必定相邻的,那么要想优化时间,不妨能够枚举中多步跳跃?
于是开始找规律:
首先,一个数i,它对答案的贡献(1~n中 约数包含i的数的个数)为 n/i下取整…(如何证明?)
十分的简单,首先,假使n%i==0,那么1~n中,约数包含i的数的个数必定是n/i,那么当n%i!=0,那些多出来的数的约数是不含i的,那么剩下的便同整除时一样。得证。。。
那么再发现一个规律, 试计算n/(n/i)(/为C++中整除),i取不同的值,于是发现,在同一个周期中(暂且称那些贡献相同的数为一个周期),上述计算值相同,且都为这个周期的末尾。
于是高效率的做法就是,每次都增加n/(n/i)步,答案用(n/(n/i)-i+1)*(n/i)来统计即可,如此效率大概是O(logn)级的,于是顺利完成。
简短的代码:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
long long ans=0;
int n;
int main()
{
scanf("%d",&n);
for (int i=1,j=0; i<=n; i=j+1)
j=n/(n/i),ans+=(j-i+1)*(n/i);
printf("%lld\n",ans);
return 0;
}