欧拉函数,筛法(最大公约数之和——极限版II,UVA 11426)

来源:互联网 发布:unity3d 刚体穿透 编辑:程序博客网 时间:2024/06/08 08:47

看到数据范围,大概就知道是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 4000010using 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;}


0 0