[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 }

 

posted @ 2017-09-26 18:55  halfrot  阅读(...)  评论(...编辑  收藏