1. 程式人生 > 實用技巧 >Luogu 1447 - [NOI2010]能量採集 (莫比烏斯反演)

Luogu 1447 - [NOI2010]能量採集 (莫比烏斯反演)

Luogu 1447 - [NOI2010]能量採集


題意

給定\(n,m\),在座標系\((0,0)\) 位置有一個能量採集器

可行範圍為\(n\times m\)內的所有整數點(不包括座標軸上)

開採每個整數點會造成\(1\)能量損失

對於每個點\((x,y)\),如果\((0,0)\)與其連線上存在著\(cnt\)個其它整數點,那麼它的能量損失將會增加\(2\times cnt\)

即對於每個點,能量損失為\(2\times cnt_{(x,y)}+1\)

\(n\times m\)範圍內所有點的能量損失之和,即求

\[\sum_{i=1}^n\sum_{j=1}^m2\times cnt_{(x,y)}+1 \]


限制

\(Case=1,\ 1\leq n,m\leq 10^5\)




思路

根據題意(或據圖可得)

每個點\((x,y)\)\((0,0)\)點連線上的其餘整數點個數即為\(\gcd(x,y)-1\)

所以所求的答案即可轉化為

\[\begin{align} &\sum_{i=1}^n\sum_{j=1}^m2\times (\gcd(i,j)-1)+1\\ =&\sum_{i=1}^n\sum_{j=1}^m2\times \gcd(i,j)-1\\ =&2\times \sum_{i=1}^n\sum_{j=1}^m(\gcd(i,j))-n\times m\\ =&2\times \sum_{d=1}^{\min(n,m)}d\times\sum_{i=1}^n\sum_{j=1}^m[\gcd(i,j)=d] -n\times m\\ =&2\times \sum_{d=1}^{\min(n,m)}d\times\sum_{i=1}^{\frac n d}\sum_{j=1}^{\frac m d}[\gcd(i,j)=1] -n\times m\\ =&2\times \sum_{d=1}^{\min(n,m)}d\times\sum_{i=1}^{\frac{\min(n,m)}{d}}\mu(i)\lfloor\frac{\lfloor\frac n d\rfloor}{i}\rfloor\lfloor\frac{\lfloor\frac m d\rfloor}{i}\rfloor -n\times m\\ \end{align} \]

直接套板子即可



程式碼

Case Max (15ms/1000ms)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=100000;

ll mu[N+50],prim[N+50];
bool vis[N+50];

void init(int n)
{
    memset(vis,false,sizeof vis);
    mu[1]=1;
    int p=0;
    for(int i=2;i<=n;i++)
    {
        if(!vis[i])
        {
            prim[p++]=i;
            mu[i]=-1;
        }
        for(int j=0;j<p;j++)
        {
            int k=i*prim[j];
            if(k>n)
                break;
            vis[k]=true;
            if(i%prim[j]==0)
            {
                mu[k]=0;
                break;
            }
            else
                mu[i*prim[j]]=-mu[i];
        }
    }
}

int main()
{
    init(N);
    int n,m;
    scanf("%d%d",&n,&m);
    ll ansd,ans=0;
    int mn=min(n,m);
    for(int d=1;d<=mn;d++)
    {
        int mnd=mn/d,a=n/d,b=m/d;
        ansd=0;
        for(int i=1;i<=mnd;i++)
            ansd+=mu[i]*(a/i)*(b/i);
        ans+=ansd*d;
    }
    printf("%lld\n",ans*2-1LL*n*m);
    
    return 0;
}