BZOJ 2005 能量采集

Description

栋栋有一块长方形的地,他在地上种了一种能量植物,这种植物可以采集太阳光的能量。在这些植物采集能量后,栋栋再使用一个能量汇集机器把这些植物采集到的能量汇集到一起。 栋栋的植物种得非常整齐,一共有\(n\)列,每列有\(m\)棵,植物的横竖间距都一样,因此对于每一棵植物,栋栋可以用一个坐标\((x,y)\)来表示,其中\(x\)的范围是\(1\)\(n\),表示是在第\(x\)列,\(y\)的范围是\(1\)\(m\),表示是在第\(x\)列的第\(y\)棵。 由于能量汇集机器较大,不便移动,栋栋将它放在了一个角上,坐标正好是\((0,0)\)。能量汇集机器在汇集的过程中有一定的能量损失。如果一棵植物与能量汇集机器连接而成的线段上有k棵植物,则能量的损失为\(2k+1\)。例如,当能量汇集机器收集坐标为\((2,4)\)的植物时,由于连接线段上存在一棵植物\((1,2)\),会产生\(3\)的能量损失。注意,如果一棵植物与能量汇集机器连接的线段上没有植物,则能量损失为1。现在要计算总的能量损失。 下面给出了一个能量采集的例子,其中\(n=5,m=4\),一共有\(20\)棵植物,在每棵植物上标明了能量汇集机器收集它的能量时产生的能量损失。在这个例子中,总共产生了36的能量损失。

Input

仅包含一行,为两个整数\(n\)\(m\)

Output

仅包含一个整数,表示总共产生的能量损失。

Sample Input

5 4

Sample Output

36

HINT

对于\(10\%\)的数据:\(1 \le n,m \le 10\)
对于\(50\%\)的数据:\(1 \le n, m \le 100\)
对于\(80\%\)的数据:\(1 \le n, m \le 1000\)
对于\(90\%\)的数据:\(1 \le n, m \le 10000\)
对于\(100\%\)的数据:\(1 \le n, m \le 100000\)

题目求$$\sum_{i=1}^{N} \sum_{j=1}^{M}2 \times gcd(i,j) - 1$$
转换变成枚举\(gcd\)求$$\sum_{g=1}^{min(N,M)}(2 \times g -1)\sum_{i = 1}^{N} \sum_{j = 1}^{M} [gcd(i,j) = g]$$
然后同BZOJ 2301 Problem b的做法进行莫比乌斯反演,就可以化成$$\sum_{g=1}{min(N,M)}\sum_{k=1} \rfloor,\lfloor \frac{M}{g} \rfloor)} \mu(k) \lfloor \frac{N}{kg} \rfloor \lfloor \frac{M}{kg} \rfloor $$
由于$$\sum_{i=1}^{N} \frac{1}{i} \approx ln N$$
所以直接求这个式子复杂度是\(O(NlogN)\)
由于人比较愚钝,所以我预处理了\(\mu\)的前缀和,还傻逼的\(\sqrt{N}\)分段了。

#include<iostream>
#include<cstdio>
#include<cstdlib>
using namespace std;

typedef long long ll;
#define maxn 100010
int n,m,tot,prime[maxn],mu[maxn];
bool exist[maxn]; ll ans;

inline void find()
{
	mu[1] = 1;
	for (int i = 2;i <= n;++i)
	{
		if (!exist[i]) prime[++tot] = i,mu[i] = -1;
		for (int j = 1;j <= tot&&i*prime[j]<=n;++j)
		{
			exist[i*prime[j]] = true;
			if (i % prime[j] == 0) { mu[i*prime[j]] = 0; break; }
			mu[i*prime[j]] = -mu[i];
		}
	}
	for (int i = 1;i <= n;++i) mu[i] += mu[i-1];
}

inline ll calc(int a,int b,int d)
{
	a /= d; b /= d;
	ll ret = 0; int pos;
	for (int i = 1;i <= a;i = pos+1)
	{
		pos = min(a/(a/i),b/(b/i));
		ret += (ll)(mu[pos]-mu[i-1])*(ll)(a/i)*(ll)(b/i);
	}
	return ret;
}

int main()
{
	freopen("2005.in","r",stdin);
	freopen("2005.out","w",stdout);
	scanf("%d %d",&n,&m); if (n > m) swap(n,m);	
	find();
	for (int i = 1;i <= n;++i)
		ans += (ll)((i<<1)-1)*calc(n,m,i);
	printf("%lld",ans);
	fclose(stdin); fclose(stdout);
	return 0;
}
posted @ 2015-07-08 20:06  lmxyy  阅读(182)  评论(0编辑  收藏  举报