看到数据范围,大概就知道是O(nlogn)了。
然后一开始尝试用筛法来做,有一些细节没办法处理好,想要处理好至少要O(n^2logn),还不如O(n^2)。
注意啊我的方法是错的,只是讲一下,别看。
我的方法就是ans[j]表示∑gcd(i,j),i<j。
然后s[i]=s[i-1]+ans[i]。
s[n]就是答案。
怎么求ans[j]呢。就是用筛法。
因为 ans[j]=∑gcd(i,j),i<j。
枚举i以及i的倍数j。这时gcd(i,j)=i。
然后ans[i]+=(j-i)/i*(i-f(i,j));
这个式子就是让每个gcd(i,j)都等于i,然后后面筛选时再部分修改。
f(i,j)就是不等于i的gcd(i,j)。表示之前错误的gcd(i,j)。
(i-f(i,j))的意思就是把错误的gcd(i,j)修改成i的修改值。
(j-i)/i就是有这么多错误的gcd。
相乘就是总的需要的修改值。更新一下ans[j]就好了。
这个方法只能得到一个近似的答案,因为每个错误的gcd(i,j)需要的修改值是不同的,不能用f(i,j)来代表全部,必须要O(n)枚举才能知道每个错误的gcd(i,j)的值。需要增加时间复杂度,这就超时了。然后就不知道该怎么处理了。
事实上我的思路应该更清晰一点的,在思考一道题的过程中会有非常多的想法,其中有很多错误的思路,虽然最后往往都能找对大方向,但是还像盲人摸象,思路太杂,互相干扰,最后在细节上失败。所以说如果有队友能一起讨论,那就能帮你缕清很多灰蒙蒙的地方,而有时候单单自己想会很迷。当然自己一定要学会自己独立解决问题,个人实力是队伍的尖刀,不要太依赖队友。
已经能想到用筛法以及求和了,不放把细节定义得更清楚一点,而不是觉得差不多是这个意思就好,否则就会进入迷的状态。
ans[j]=∑gcd(i,j),i<j。直接算就是朴素算法,想要另辟蹊径就得重新分类然后求和。
不妨设i是j的因数,设g(i,j)表示小于j的数x中满足gcd(x,j)=i的x的个数。(感觉最近接触了很多这种状态定义的方法,一维表示小于,二维表示确定的值,状态转移也容易实现。)
那么就枚举因数i咯,ans[j]=∑i*g(i,j),其中i是j的因数。
枚举因数时间复杂度是O(sqrt(j)),比O(j)好。
而g(i,j)该怎么求呢?状态转移方程不是那么好找。
我们应该学会发现一些特点,那就是j是i的倍数,而我们要找gcd(x,j)=i。那x肯定是i的倍数,其实就是找gcd(x/i,j/i)=1的x的个数,这不就是phi[j/i]吗。可以预处理然后O(1)求得。
然而还是不够快,但是关于枚举因数,我们可以用筛法来优化。
枚举因数i和它的倍数j,更新ans[j]就好了。
预处理O(nlogn)。
筛法O(nlogn)。
总时间复杂度O(nlogn)。
代码
#include<bits/stdc++.h>
#define maxn 4000010
using namespace std;
typedef long long ll;
ll phi[maxn];
void phi_table()
{
phi[1]=1;
for(ll i=2;i<=4000000;i++) if(!phi[i])
for(ll j=i;j<=4000000;j+=i)
{
if(!phi[j]) phi[j]=j;
phi[j]=phi[j]/i*(i-1);
}
}
ll f[maxn];
ll s[maxn];
void init()
{
for(ll i=1;i<=4000000;i++)
for(ll j=i+i;j<=4000000;j+=i)
f[j]+=i*phi[j/i];
for(ll i=1;i<=4000000;i++)
s[i]=s[i-1]+f[i];
}
int main()
{
phi_table();
init();
ll N;
while(scanf("%I64d",&N)==1&&N)
printf("%I64d\n",s[N]);
return 0;
}