P1447 [NOI2010] 能量采集

题目传送门

博客传送门-欢迎光顾喵

题意简化

首先题面有亿点点复杂。我们稍微简化一下。

首先,对于一个点对 \((x,y)\) ,我们可以快捷计算它的贡献:记 \(x=ma,y=mb\) ,其中 \(m=gcd(x,y),gcd(a,b)=1\) ,那么和 \((x,y)\) 共线的点就是 \((a,b),(2a,2b),(3a,3b), \cdots ,[(m-1)a,(m-1)b],(ma,mb)\)

也就是说, \((x,y)\)前面有 \(m-1\) 个点。所以它对答案的贡献就是 \(2*(m-1)+1\) ,即 \(2*m-1\)

同理呢,对于任意的点对 \((i,j)\) ,它对答案的贡献就是 \(2 \times gcd(i,j)-1\)

这样,原问题等同于求 \(\sum\limits_{i=1}^{n} {\sum\limits_{j=1}^{m} {[2 \times gcd(i,j)-1]}}\) ,也就是 \(2 \times \sum\limits_{i=1}^{n} {\sum\limits_{j=1}^{m} {gcd(i,j)}} - n \times m\)


分析

按照上面化简式直接暴力枚举 \(i,j\) ,时间复杂度是 \(O(n^2 \log n)\) 的,喜提 80pts 。

但是既然你点开了我的题解就肯定是来看正解的对吧

我们直接去枚举两个数的 \(gcd\) 会是多少,这样的话,答案就是 \((2*gcd-1)*num_{gcd}\) ,其中 \(num_{gcd}\) 表示最大公约数\(gcd\) 的数对有多少个。

如果求公约数\(gcd\) 的数对是好求的,答案就是 \(\lfloor \frac {n}{gcd} \rfloor \times \lfloor \frac {m}{gcd} \rfloor\)

(这个很显然,因为 \(1\) ~ \(n\)\(gcd\) 的倍数有 \(\lfloor \frac {n}{gcd} \rfloor\) 个, \(1\) ~ \(m\)\(gcd\) 的倍数有 \(\lfloor \frac {m}{gcd} \rfloor\) 个。根据乘法原理,数对个数是两个个数相乘。

但是这里面一定会存在最大公约数是 $2 \times gcd,3 \times gcd,4 \times gcd, \cdots $ 的,重叠部分怎么处理呢?如果我们有了 $num_{2 \times gcd},num_{3 \times gcd},num_{4 \times gcd}, \cdots $ ,直接枚举它的倍数,减掉这些数对的个数就行了。

想到这里,我们倒序枚举可能的 \(gcd\) ,根据如上思路求解即可。容易观察到枚举 \(gcd\) 倍数的过程中带一个调和级数,故最终时间复杂度为 \(O(n \log n)\)

代码:

P1447
#include<bits/stdc++.h>
#define int long long
using namespace std;

inline int read(){
	int x=0,f=1;char c=getchar();
	while(c<48){
		if(c=='-') f=-1;
		c=getchar();
	}
	while(c>47) x=(x<<1)+(x<<3)+(c^48),c=getchar();
	return x*f;
}

inline void write(int x){
	if(x<0) putchar('-'),x=-x;
	if(x<10) putchar(x+'0');
	else write(x/10),putchar(x%10+'0');
}

const int N=1e5+5;
int n,m,ans,num[N];

signed main(){
	n=read(),m=read();
	//gcd的范围是 1~min(m,n),我们假定n是小的那个 
	if(n>m) swap(n,m);
	//如果n比m大就直接交换 
	for(int gcd=n;gcd;gcd--){
		num[gcd]=(n/gcd)*(m/gcd);
		for(int i=2*gcd;i<=min(n,m);i+=gcd){//枚举gcd的倍数i 
			num[gcd]-=num[i];//减掉算多的数对 
		}
		ans=ans+(2*gcd-1)*num[gcd];//累加答案 
	}
	printf("%lld",ans);
	return 0;
}
posted @ 2025-10-20 18:57  qwqSW  阅读(5)  评论(0)    收藏  举报