【SPOJ7001】Visible Lattice Points-莫比乌斯反演+分块

本文介绍了一种算法,用于计算三维空间中特定条件下可见点的数量。通过将问题转化为数学表达式,并运用莫比乌斯反演进行优化,实现了高效求解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

测试地址:Visible Lattice Points
题目大意:在三维空间中,我们说一个点是可见的当且仅当它与点 (0,0,0) 连成的线段不经过任何其他坐标为整数的点。有 T(T50) 组询问,每组询问给出一个参数 N ,意为询问在所有点(x,y,z)(0x,y,zN,1N1106)中,有多少个可见的点,其中 x,y,z 为整数。特别地, (0,0,0) 不算作可见的点。
做法:这一道题是POJ3090的加强版,从二维扩展到了三维,我写的POJ3090题解在此。而这题就不能简单地使用欧拉函数的性质来解决问题了,而需要用莫比乌斯反演来解决。
将点分为三种情况:1. x,y,z 均不为0。2. x,y,z 中只有一个是0。3. x,y,z 中只有一个不是0。对于第一种情况,很容易看出当 gcd(x,y,z)=1 时,点 (x,y,z) 是可见的,那么我们就是要求下面这个式子的值(注意,下文中方括号 [] 表示如果括号内的式子为真,值为1,否则值为0):

x=1Ny=1Nz=1N[gcd(x,y,z)=1]

显然暴力求这个式子会炸,这时候我们就要用到一个莫比乌斯函数的性质: d|nμ(d)=[n=1] ,这个性质使得我们可以把上式化成:
Nx=1Ny=1Nz=1d|gcd(x,y,z)μ(d)
分析第四个求和号下面的条件,显然 d|gcd(x,y,z) 的充要条件是 d|xd|yd|z ,那么上式就可以化成:
Nd=1μ(d)1xNd|x1yNd|y1zNd|z1
这个式子等价于:
Nd=1μ(d)(1xNd|x1)×(1yNd|y1)×(1zNd|z1)
那么这个式子显然等于:
Nd=1μ(d)Nd3
这就是第一种情况的答案了。第二种情况就是要分别求 gcd(x,y)=1,gcd(y,z)=1,gcd(x,z)=1 的点数,式子的推导和上面比较类似,这里就不再赘述了。第三种情况显然只有 (0,0,1),(0,1,0),(1,0,0) 三个点。这样我们就找到了一个可以 O(N) 计算单个询问答案的方法。但是 O(TN) 的总复杂度对于严苛的时间限制好像有点拙计,需要想办法优化。注意到 Nd 的值不会超过 2N 种,而且对于同一个 Nd 的值,满足条件的 d 是一个连续的区间,这启发我们利用分块思想优化,问题转变为如何求Nd值相等的区间。思考对于任意一个 d ,求满足Nd=Np的最大的 p 。令q=Nd,那么 N=dq+r0=pq+r1 ,我们知道在 q 相等的情况下,r1越逼近 0 p就越大,所以最大的 p=Nq=NNd 。那么我们再预处理出 μ(i) 的前缀和就可以把单个询问的时间复杂度加速到 O(N) ,处理询问的总时间复杂度为 O(TN) ,可以通过此题。
以下是本人代码:

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#define ll long long
using namespace std;
int T;
ll N,mu[1000010],sum[1000010];
bool prime[1000010]={0};

void calc_mu()
{
  N=1000000;
  for(int i=1;i<=N;i++) mu[i]=1;
  for(int i=2;i<=N;i++)
    if (!prime[i])
    {
      for(int j=1;i*j<=N;j++)
      {
        mu[i*j]*=-1;
        if (j>1) prime[i*j]=1;
        if (!(j%i)) {mu[i*j]=0;continue;}
      }
    }
  sum[0]=0;
  for(int i=1;i<=N;i++) sum[i]=sum[i-1]+mu[i];
}

int main()
{
  scanf("%d",&T);
  calc_mu();
  while(T--)
  {
    scanf("%lld",&N);
    ll ans=3,last;
    for(ll d=1;d<=N;d=last+1)
    {
      last=N/(N/d);
      ans+=((N/d)+3)*(N/d)*(N/d)*(sum[last]-sum[d-1]);
    }
    printf("%lld\n",ans);
  }

  return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值