1. 程式人生 > >[BZOJ2005][NOI2010]能量採集(莫比烏斯反演)

[BZOJ2005][NOI2010]能量採集(莫比烏斯反演)

題目描述

傳送門

題解

首先證明對於某個點(x,y),k=gcd(x,y)-1:
設gcd(x,y)=t,令x=at,y=bt,那麼在這條直線上的整數點可以表示為(a,b)(2a,2b)(3a,3b)……(x,y),由於不算x,y,則答案為gcd(x,y)-1
那麼總損耗2k+1=2×gcd(x,y)-1。
我們最終要求的式子為:
i=1nj=1m(gcd(i,j)21)
=2i=1nj=1mgcd(i,j)nm
那麼我們只需要算出i=1nj=1mgcd(i,j)這個式子就可以了
推導如下:
i=1nj=1mgcd(i,j)
=i=1n

j=1md|gcd(i,j)ϕ(d)
=i=1nj=1md=1n[d|i][d|j]ϕ(d)
=d=1ni=1n[d|i]j=1m[d|j]ϕ(d)
=d=1nndmdϕ(d)

實際上ndmd只有(n+m)個取值。
可以用分塊來求。
需要預處理phi的字首和。

程式碼

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
#define LL long long

const int max_n=1
e5+5; LL n,m,ans; LL p[max_n],phi[max_n],prime[max_n]; inline void get_phi(){ phi[1]=1; for (int i=2;i<=n;++i){ if (!p[i]){ prime[++prime[0]]=i; phi[i]=i-1; } for (int j=1;j<=prime[0]&&i*prime[j]<=n;++j){ p[i*prime[j]]=1
; if (i%prime[j]==0){ phi[i*prime[j]]=phi[i]*prime[j]; break; } else phi[i*prime[j]]=phi[i]*(prime[j]-1); } phi[i]+=phi[i-1]; } } int main(){ scanf("%lld%lld",&n,&m); if (n>m) swap(n,m); get_phi(); for (LL i=1,j;i<=n;i=j+1){ j=min(n/(n/i),m/(m/i)); ans+=(LL)(phi[j]-phi[i-1])*(n/i)*(m/i); } printf("%lld\n",ans*2-n*m); }

總結

首先Orz ATP
求phi的時候不要打phi[prime[j]],尤其是求字首和的時候。