BZOJ 2005 [Noi2010]能量采集
2005: [Noi2010]能量采集
Description
栋栋有一块长方形的地,他在地上种了一种能量植物,这种植物可以采集太阳光的能量。在这些植物采集能量后,栋栋再使用一个能量汇集机器把这些植物采集到的能量汇集到一起。 栋栋的植物种得非常整齐,一共有n列,每列有m棵,植物的横竖间距都一样,因此对于每一棵植物,栋栋可以用一个坐标(x, y)来表示,其中x的范围是1至n,表示是在第x列,y的范围是1至m,表示是在第x列的第y棵。 由于能量汇集机器较大,不便移动,栋栋将它放在了一个角上,坐标正好是(0, 0)。 能量汇集机器在汇集的过程中有一定的能量损失。如果一棵植物与能量汇集机器连接而成的线段上有k棵植物,则能量的损失为2k + 1。例如,当能量汇集机器收集坐标为(2, 4)的植物时,由于连接线段上存在一棵植物(1, 2),会产生3的能量损失。注意,如果一棵植物与能量汇集机器连接的线段上没有植物,则能量损失为1。现在要计算总的能量损失。 下面给出了一个能量采集的例子,其中n = 5,m = 4,一共有20棵植物,在每棵植物上标明了能量汇集机器收集它的能量时产生的能量损失。 在这个例子中,总共产生了36的能量损失。
Input
仅包含一行,为两个整数n和m。
Output
仅包含一个整数,表示总共产生的能量损失。
Sample Input
【样例输入1】
5 4
【样例输入2】
3 4
5 4
【样例输入2】
3 4
Sample Output
【样例输出1】
36
【样例输出2】
20
对于100%的数据:1 ≤ n, m ≤ 100,000
36
【样例输出2】
20
对于100%的数据:1 ≤ n, m ≤ 100,000
我们本来只打算讲一个晚上的数论,但是时间并不够。后来,又讲了一个上午,然而并没有讲完。于是下午再讲,基本上已无人再听。
身心俱疲。课件在这里
这道题要求一个n*m的矩阵中各整点与(0,0)连线上的整点总数。
由BZOJ 3505 [Cqoi2014]数三角形中推得,(x,y)与(0,0)上有k=gcd(x,y)-1个整点。而此处要求∑∑k*2+1=∑∑gcd(i,j)*2-1
这道题:∑∑gcd(i,j)=∑∑∑[d|i][d|j]phi(d)=∑(n/d)(m/d)phi(d)
因为:n=∑phi(d)。
明显先用欧拉筛,算出phi的前缀和。再分块,一个块中(n/i)(m/i)都相等。所以是O(n)预处理,O(sqrt(n))的单次询问(当然O(n)亦可)。
但是我们也可以使用容斥原理,算出∑∑gcd(i,j)。复杂度是O(nlg n)的。
代码如下(调了很久……前缀和导致的变化,long long的取值):
1 /************************************************************** 2 Problem: 2005 3 User: Doggu 4 Language: C++ 5 Result: Accepted 6 Time:4 ms 7 Memory:2092 kb 8 ****************************************************************/ 9 10 #include <cstdio> 11 #include <algorithm> 12 const int N = 100100; 13 int n, m, prime[N], ptot; 14 long long phi[N], ans; 15 bool vis[N]; 16 void EULER(int n) { 17 phi[1]=1; 18 for( int i = 2; i <= n; i++ ) { 19 if(!vis[i]) prime[++ptot]=i, phi[i]=i-1; 20 for( int j = 1; j <= ptot; j++ ) { 21 if((long long)i*prime[j]>n) break; 22 vis[i*prime[j]]=1; 23 phi[i*prime[j]]=phi[i]*(prime[j]-1); 24 if(i%prime[j]==0) { 25 phi[i*prime[j]]=phi[i]*prime[j]; 26 break; 27 } 28 } 29 phi[i]+=phi[i-1]; 30 } 31 } 32 int main() { 33 scanf("%d%d",&n,&m); 34 if(n>m) std::swap(n,m); 35 EULER(n); 36 for( int a = 1, ed; a <= n; a=ed+1 ) { 37 ed=std::min(n/(n/a),m/(m/a)); 38 ans+=(long long)(phi[ed]-phi[a-1])*(n/a)*(m/a); 39 } 40 printf("%lld\n",ans*2-(long long)n*m); 41 return 0; 42 }