jzoj3509-倒霉的小C【gcd,欧拉函数】

正题


大意

画n条线,每次坐标变换为 ( x + n , y + ( 1 ) ( i + 1 ) i )       ( i = 1 n ) 。给出n,求线穿过的格点数。


解题思路

首先我们想穿过格点的问题,我们可以无视方向,然后每次就当从 ( 0 , 0 ) ( n , i ) 划一条线。然后我们可以发现穿过的格点数(算上起点)就是 g c d ( n , i ) ,因为每次都会穿过 ( n / j , i / j )       ( j = 1 g c d ( n , i ) ) 这些点。
然后我们可以知道答案就是

1 + i = 1 n g c d ( n , i )

但是暴力枚举会超时,我们需要想别的方法
i = 1 n g c d ( n , i ) = d

=> d = 1 n d i = 1 n g c d ( i , n ) = d

d = 1 n d i = 1 n g c d ( i / d , n / d ) = 1

因为要求 g c d ( i / d , n / d ) = 1 ,所以i的个数就是 φ ( n / d )
所以答案就是
d = 1 n d × φ ( n / d )

由于要求 n / d 是整数,所以我们可以化为
1 + d | n d × φ ( n / d )

然后暴力枚举约数和暴力求欧拉函数值时间复杂度为 O ( n     l o g   n )


代码

#include<cstdio>
#include<algorithm>
#define ll long long
using namespace std;
ll n,ans=1;
ll phi(ll n)//求欧拉函数
{
    ll ans=n;
    for (ll i=2;i*i<=n;i++)
      if (n%i==0)
      {
        ans=ans/i*(i-1);
        while (n%i==0) n/=i;
      }
    if (n>1) ans=ans/n*(n-1);
    return ans;
}
int main()
{
    //freopen("beats.in","r",stdin);
    //freopen("beats.out","w",stdout);
    scanf("%lld",&n);
    for (ll i=1;i*i<=n;i++)
    {
      if (!(n%i)) 
      {
        ans+=phi(n/i)*i;
        if (i*i!=n) ans+=n/i*phi(i);
      }//求约数
    }
    printf("%lld",ans);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/mr_wuyongcong/article/details/81104903