1. 程式人生 > 實用技巧 >P1447能量採集

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