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

正题


大意

画n条线,每次坐标变换为 ( x + n , y + ( − 1 ) ( i + 1 ) ∗ i )     ( i = 1 ∼ n ) (x+n,y+(-1)^{(i+1)}*i) \ \ \ (i=1\sim n) (x+n,y+(1)(i+1)i)   (i=1n)。给出n,求线穿过的格点数。


解题思路

首先我们想穿过格点的问题,我们可以无视方向,然后每次就当从 ( 0 , 0 ) (0,0) (0,0) ( n , i ) (n,i) (n,i)划一条线。然后我们可以发现穿过的格点数(算上起点)就是 g c d ( n , i ) gcd(n,i) gcd(n,i),因为每次都会穿过 ( n / j , i / j )     ( j = 1 ∼ g c d ( n , i ) ) (n/j,i/j)\ \ \ (j=1\sim gcd(n,i)) (n/j,i/j)   (j=1gcd(n,i))这些点。
然后我们可以知道答案就是
1 + ∑ i = 1 n g c d ( n , i ) 1+\sum_{i=1}^n gcd(n,i) 1+i=1ngcd(n,i)
但是暴力枚举会超时,我们需要想别的方法
∑ i = 1 n g c d ( n , i ) = d \sum_{i=1}^n gcd(n,i)=d i=1ngcd(n,i)=d
= > ∑ d = 1 n d ∗ ∑ i = 1 n ( g c d ( i , n ) = = d ) =>\sum_{d=1}^n d*\sum ^n_{i=1}(gcd(i,n)==d) =>d=1ndi=1n(gcd(i,n)==d)
∑ d = 1 n d ∗ ∑ i = 1 n g c d ( i / d , n / d ) = 1 \sum_{d=1}^n d*\sum ^n_{i=1}gcd(i/d,n/d)=1 d=1ndi=1ngcd(i/d,n/d)=1
因为要求 g c d ( i / d , n / d ) = 1 gcd(i/d,n/d)=1 gcd(i/d,n/d)=1,所以i的个数就是 φ ( n / d ) \varphi(n/d) φ(n/d)
所以答案就是
∑ d = 1 n d × φ ( n / d ) \sum_{d=1}^n d\times \varphi(n/d) d=1nd×φ(n/d)
由于要求 n / d n/d n/d是整数,所以我们可以化为
1 + ∑ d ∣ n d × φ ( n / d ) 1+\sum_{d|n} d\times \varphi(n/d) 1+dnd×φ(n/d)
然后暴力枚举约数和暴力求欧拉函数值时间复杂度为 O ( n    l o g   n ) O(\sqrt n\ \ log\ n) O(n   log n)


代码

#include
#include
#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;
}

你可能感兴趣的:(jzoj3509-倒霉的小C【gcd,欧拉函数】)