BZOJ 2005: [Noi2010]能量采集 莫比乌斯反演

化简一下 $\Rightarrow$$2\times \sum_{i=1}^{n}\sum_{j=1}^{m}(gcd(i,j)-1)$
 
稍微展开一下:  
 
$2\times\sum_{i=1}^{n}\sum_{j=1}^{m}gcd(i,j)-2\times\sum_{i=1}^{n}\sum_{j=1}^{m}1$
 
$\Rightarrow 2\times\sum_{i=1}^{n}\sum_{j=1}^{m}gcd(i,j)-n\times m$
 
只需快速求出 $re=\sum_{i=1}^{n}\sum_{j=1}^{m}gcd(i,j)$ 即可. 
 
这个就十分好推了吧.......
 
$\Rightarrow \sum_{d=1}^{n}d\sum_{i=1}^{n}\sum_{j=1}^{m}[gcd(i,j)==d]$
 
后面就是 $zap$ 了,可以很简单地求出,这里还是给一下推导:
 
$\sum_{i=1}^{n}\sum_{j=1}^{m}[gcd(i,j)==d]$
 
$\Rightarrow \sum_{i=1}^{\frac{n}{d}} \sum_{j=1}^{\frac{m}{d}}[gcd(i,j)==1]$
 
$\Rightarrow \sum_{i=1}^{\frac{n}{d}} \sum_{j=1}^{\frac{m}{d}}\sum_
{b|i,b|j}\mu(b)$
 
$\Rightarrow \sum_{b=1}^{\frac{n}{d}}\mu(b)\sum_{b|i}\sum_{b|j}$
 
发现 $\sum_{b|i}^{\frac{n}{d}}\sum_{b|j}^{\frac{m}{d}}=\frac{n}{db}\times\frac{m}{db}$ 
 
 
原式 $=\sum_{b=1}^{\frac{n}{d}}\mu(b)\frac{n}{db}\frac{m}{db}$
 
整除分块可在 $O(n)$ 时间内求解.
 
回到之前 $\sum_{d=1}^{n}d\sum_{i=1}^{n}\sum_{j=1}^{m}[gcd(i,j)==d]$
 
定义 $calc(n,m)=\sum_{i=1}^{n}\sum_{j=1}^{m}[gcd(i,j)==1]$
 
$\Rightarrow re=\sum_{d=1}^{n}d\times calc(\frac{n}{d},\frac{m}{d})$
 
这个也可以整除分块来算.
 
时间复杂度为 $O(n)$ 的.
 
最终答案为 $2\times re-n\times m$
#include<bits/stdc++.h>
#define ll long long 
#define maxn 100104 
#define M 100002 
using namespace std;
inline void setIO(string s)
{
	string in=s+".in"; 
	freopen(in.c_str(),"r",stdin); 
} 
int cnt; 
bool vis[maxn]; 
int mu[maxn], prime[maxn]; 
ll sumv[maxn],sum2[maxn];  
inline ll calc(ll n,ll m)
{
	ll re=0; 
	int i,j;
	for(i=1;i<=n;i=j+1)
	{
		j=min(n/(n/i), m/(m/i)); 
		re+=(sumv[j]-sumv[i-1])*(n/i)*(m/i); 
	}
	return re; 
}
inline ll solve(ll n,ll m)
{
	ll re=0; 
	int i,j;
	for(i=1;i<=n;i=j+1)
	{
		j=min(n/(n/i), m/(m/i)); 
		re+=(sum2[j]-sum2[i-1])*calc(n/i,m/i); 
	}
	return re; 
}
int main()
{
	// setIO("input"); 
	int i,j,T,n,m; 
	mu[1]=1; 
	for(i=2;i<=M;++i)
	{
		if(!vis[i]) prime[++cnt]=i, mu[i]=-1; 
		for(j=1;j<=cnt&&1ll*prime[j]*i<=M;++j) 
		{
			vis[prime[j]*i]=1; 
			if(i%prime[j]==0) 
			{
				mu[i*prime[j]]=0; 
				break; 
			}
			mu[i*prime[j]]=-mu[i]; 
		}
	}  
	for(i=1;i<=M;++i) sumv[i]=sumv[i-1]+1ll*mu[i], sum2[i]=sum2[i-1]+1ll*i; 
	scanf("%d%d",&n,&m); 
	if(n>m) swap(n,m); 
	printf("%lld\n",solve(n,m)*2-1ll*n*m); 
	return 0; 
}

  

posted @ 2019-06-26 11:05  EM-LGH  阅读(153)  评论(0编辑  收藏  举报