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

浙公网安备 33010602011771号