[NOI2010]能量採集(莫比烏斯反演)
阿新 • • 發佈:2018-12-22
NOI2010能量採集 題解
讀完題之後我們發現在每個產生貢獻的點\((x1,y1)\)中,它與原點之間的點\((x2,y2)\)都滿足\(x2|x1\),\(y2|y1\)。現在我們要求它與原點之間點的個數,也就是這個點\((x,y)\)最大可以被除以多少——肯定是\(gcd(x1,y1)\)啊。
所以我們就知道怎麼做啦:\(2\times \sum_{i=1}^n\times \sum_{j=1}^m\times gcd(i,j)-n\times m\)
中間的那個可以用莫比烏斯反演做!
設f(i)表示x,y最大公約數為i的個數。設F(i)表示x,y存在i這個公約數。
因為\(f(i)=sum_{i|j}\times F(j)\),上限為\(min(n,m)\)
所以\(F(i)=\sum_{i|j}\mu(j/i)\times f(j)\)
f(i)不太好做,但是F(i)卻很容易算出來(具體怎麼算大家可以參考HDU GCD這個題)
所以我們預處理出\(\mu\)函式的值,然後按照套路做就可以啦qwq
程式碼如下:
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> #include<cmath> #define MAXN 100010 using namespace std; int n,m,cnt; int vis[MAXN],mu[MAXN],prime[MAXN]; long long ans; long long f[MAXN],F[MAXN]; inline void get_mu() { vis[1]=mu[1]=1; for(int i=2;i<=MAXN;i++) { if(vis[i]==0) mu[i]=-1,prime[++cnt]=i; for(int j=1;j<=cnt&&i*prime[j]<=MAXN;j++) { vis[i*prime[j]]=1; if(i%prime[j]) mu[i*prime[j]]=-mu[i]; else {mu[i*prime[j]]=0;break;} } } } int main() { #ifndef ONLINE_JUDGE freopen("ce.in","r",stdin); #endif scanf("%d%d",&n,&m); get_mu(); if(n>m) swap(n,m); for(int i=1;i<=n;i++) F[i]=1ll*(n/i)*(m/i); for(int i=1;i<=n;i++) for(int j=i;j<=n;j+=i) f[i]+=1ll*mu[j/i]*F[j]; for(int i=1;i<=n;i++) ans+=1ll*f[i]*i; ans=ans*2-1ll*n*m; printf("%lld\n",ans); return 0; }