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