P1447能量采集

P1447能量采集

  • 定义:(i,j)表示处于(i,j)的植物的贡献

我们发现,点(i,j)与(0,0)的连线所过整点的数目为(gcd(i,j))

发现要是想记录每个点的答案并不好算。那么怎么好算呢?

我们来找一找同一直线上的所有点答案的和的关系。先不考虑答案只考虑个数。发现,寻找一个点及其倍数的个数的和更加好算。而且,因为有n和m的限制,那么向下取整的答案一定就是其本身。考虑容斥,我们只需要从大往小更新答案并将答案乘2减1加起来即可。

那么对于一个点及其倍数的答案怎么计算呢?

假设n小于m,那么对于一个小于n的数i,显然它的倍数的个数就是((n/i)*(m/i)),这样一来我们只需要考虑小于n的所有数的个数就能够统计n*m的所有数的答案了。至于为什么((m-n) * m)这一块不用考虑,是因为这里不会再有数容斥它们了,直接统计就行。

所以,答案即为

[displaystyle sum_{i=1}^{n}num_i*(i*2-1) ]

其中(displaystyle num_i=(n/i)*(m/i)-sum_{i=2}^{n/i}num_i)

在代码中一个倒序循环即可,时间复杂度线性。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cctype>
#include<cstring>
#define int long long 
using namespace std;
inline int read(){
	int x=0,w=0;char c=getchar();
	while(!isdigit(c))w|=c=='-',c=getchar();
	while(isdigit(c))x=(x<<3)+(x<<1)+(c^48),c=getchar();
	return w?-x:x;
}
const int maxn=1e5+10;
int ans[maxn];
signed main(){
	int n=read(),m=read(),Ans=0;
	if(n>m)swap(n,m);
	for(int i=n;i;i--){
		ans[i]=(n/i)*(m/i);
		for(int j=2;j<=n/i;j++)ans[i]-=ans[i*j];
		Ans+=(ans[i]*(i*2-1));
	}
	printf("%lld",Ans);
	return 0;
}
原文地址:https://www.cnblogs.com/BrotherHood/p/13543695.html