[BZOJ2005][NOI2010]能量采集 数学
题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=2005
发现与$(0,0)$连线斜率相同的点会被挡住。也就是对于$(a,b)$且$gcd(a,b)==1$,在这条连线上$(da,db)$都会被挡住。
换种表达方式就是对于任意一个点$(x,y)$,会有$gcd(x,y)-1$个点被挡住。于是被挡住的点的个数是$$\sum_{i=1}^n\sum_{j=1}^mgcd(i,j)-1$$
即答案为$$Ans=\sum_{i=1}^n\sum_{j=1}^m2*gcd(i,j)-1$$
$$\frac{Ans-n*m}{2}=\sum_{i=1}^n\sum_{j=1}^mgcd(i,j)$$
现在的问题就在于如何快速求右边的东西。我们来接着推。
首先有一个东西$n=\sum_{d|n}φ(d)$,于是有$\sum_{i=1}^n\sum_{j=1}^mgcd(i,j)=$
$$\sum_{i=1}^n\sum_{j=1}^m\sum_{d|gcd(i,j)}φ(d)=$$
$$\sum_{d=1}^{min(n,m)}φ(d)\sum_{i=1}^n[d|i]\sum_{j=1}^m[d|j]=$$
$$\sum_{d=1}^{min(n,m)}φ(d)[\frac{n}{d}][\frac{m}{d}]$$
然后线性筛求欧拉函数,分块求和就行了。
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 typedef long long ll; 6 bool vis[100010]; 7 int p[100010],pcnt=0; 8 int phi[100010]; 9 ll sum[100010]; 10 void sieve(){ 11 sum[1]=1; 12 for(int i=2;i<=100000;i++){ 13 if(!vis[i]){ 14 vis[i]=true; 15 p[++pcnt]=i; 16 phi[i]=i-1; 17 } 18 for(int j=1;p[j]*i<=100000&&j<=pcnt;j++){ 19 vis[p[j]*i]=true; 20 if(i%p[j]==0){ 21 phi[i*p[j]]=phi[i]*p[j]; 22 break; 23 } 24 phi[i*p[j]]=phi[i]*(p[j]-1); 25 } 26 sum[i]=sum[i-1]+phi[i]; 27 } 28 } 29 int main(){ 30 sieve(); 31 int n,m; 32 scanf("%d%d",&n,&m); 33 int t=min(n,m),la; 34 ll ans=0; 35 for(int i=1;i<=t;i=la+1){ 36 la=min(n/(n/i),m/(m/i)); 37 ans+=(ll)(sum[la]-sum[i-1])*(n/i)*(m/i); 38 } 39 ans=ans*2-(ll)n*m; 40 printf("%lld\n",ans); 41 return 0; 42 }