SPOJ 7001 Visible Lattice Points (莫比乌斯反演)

题意:给定n*n*n的立方体,问从(0,0,0)点处能看到多少点,一个点能被看到当且仅当它与原点之间的连线上没有其他点。

思路:假设一个点(x,y,z)能被看到,那么gcd(x,y,z)一定为1,这是因为如果这三者有公因数,那么除以公因数后的这个点一定在它与原点之间的连线上,所以我们要求的等价于(0,n)中任取三点(可以相等),且这三者的gcd为1的点的数量。

这个问题就很类似经典莫比乌斯反演问题了,但是有0这个数不好办,对此,我们可以把整个立方体空间分成三部分

第一部分是(x>=1, y>=1, z>=1)的三维空间,对于这一部分我们可以用莫比乌斯反演来解决。

第二部分是(x=0, y>=1, z>=1),(x>=1, y=0, z>=1),(x>=1, y>=1, z=0)的区域,也就是三个等价的二维空间,也可以用莫比乌斯反演来求。

第三部分是三条坐标轴,对此我们直接在答案上加三即可。

这道题还有很重要的一点就是优化,处理莫比乌斯函数的前缀和然后分段求和可以大大优化运行时间,亲测不优化4970ms,优化后10ms,以前一直没重视这个优化,现在看来这个优化是很有必要的。

#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<vector>
#include<map>
#include<queue>
#include<stack>
#include<string>
#include<map>
#include<set>
#include<ctime>
#define eps 1e-6
#define LL long long
#define pii pair<int, int>
using namespace std;
const int MAXN = 1000000+1000;
bool check[MAXN+10];
int prime[MAXN+10];
int mu[MAXN+10], Sum[MAXN+10];
void Moblus()
{
    memset(check,false,sizeof(check));
    mu[1] = 1;
    int tot = 0;
    for(int i = 2; i <= MAXN; i++)
    {
        if( !check[i] )
        {
            prime[tot++] = i;
            mu[i] = -1;
        }
        for(int j = 0; j < tot; j++)
        {
            if(i * prime[j] > MAXN) break;
            check[i * prime[j]] = true;
            if( i % prime[j] == 0)
            {
                mu[i * prime[j]] = 0;
                break;
            }
            else
            {
                mu[i * prime[j]] = -mu[i];
            }
        }
    }
    for(int i = 1; i < MAXN; i++) Sum[i] = Sum[i-1] + mu[i];
} 
int main() {
    //freopen("input.txt", "r", stdin);
	int T; cin >> T; 
	int n;
	Moblus();
	while(T--) {
		scanf("%d", &n);
		LL ans = 0;
		int last;
		for(int i = 1; i <= n; i = last+1) {
			last = n/(n/i);
			ans += (LL)(Sum[last]-Sum[i-1])*(n/i)*(n/i)*(n/i);
		}
		for(int i = 1; i <= n; i = last+1) {
			last = n/(n/i);
			ans += (LL)(Sum[last]-Sum[i-1])*(n/i)*(n/i)*3;
		}	
		cout << ans+3 << endl;
	}
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值