BZOJ 2005: [Noi2010]能量采集

二次联通门 : BZOJ 2005: [Noi2010]能量采集

 

 

 

$\LARGE Answer=2*(\sum _{i=1}^{n}\sum_{j=1}^m\gcd(i,j))-nm$

 

重点是求$\LARGE \sum ^{n}_{i=1}\sum ^{m}_{j=1}\gcd \left( i,j\right)$

 
由定理 : 一个数等于它约数的欧拉函数之和
 
 
$\LARGE n=\sum _{d|n}\varphi \left( d\right)$
 
 
可知:
 
 
   $\LARGE \sum ^{n}_{i=1}\sum ^{m}_{j=1}\gcd \left( i,j\right)$
 
$\LARGE =\sum ^{n}_{i=1}\sum ^{m}_{j=1}\sum _{d|\gcd \left( i,j\right) }\varphi \left( d\right)$
 
$\LARGE =\sum ^{\max \left( n,m\right) }_{d=1}\varphi \left( d\right) \sum ^{n/d}_{i=1}\sum ^{m/d}_{j=1}1$
 
$\LARGE =\sum ^{\max \left( n,m\right) }_{d=1}\varphi \left( n\right) \lfloor \dfrac {n}{d}\rfloor \lfloor \dfrac {m}{d}\rfloor$
 

然后除法分块,对欧拉函数做前缀和即可 

 

/*
    BZOJ 2005: [Noi2010]能量采集

    莫比乌斯反演
*/
#include <cstdio>
#include <iostream>
#include <cmath>

#define rg register
#define Max 1000005
int p[Max], phi[Max];
bool is[Max];
typedef long long LL;
LL s[Max];
void Euler (int N)
{
    int C = 0; phi[1] = 1; rg int i, j;
    for (i = 2; i <= N; ++ i)
    {
        if (!is[i]) p[++ C] = i, phi[i] = i - 1;
        for (j = 1; j <= C && i * p[j] <= N; ++ j)
        {
            is[i * p[j]] = true;
            if (i % p[j] == 0) phi[i * p[j]] = phi[i] * p[j];
            else phi[i * p[j]] = phi[i] * (p[j] - 1);
        }
    }
    for (i = 1; i <= N; ++ i) s[i] = s[i - 1] + phi[i];
}
inline int min (int a, int b) { return a < b ? a : b; }

int main (int argc, char *argv[])
{
    int N, M; scanf ("%d%d", &N, &M); rg int i, j; 
    if (N > M) std :: swap (N, M);
    Euler (N); 
    LL Answer = 0;
    for (i = 1; i <= N; i = j + 1)
    {
        j = min (N / (N / i), M / (M / i));
        Answer += (LL) (s[j] - s[i - 1]) * (N / i) * (M / i);
    }
    Answer = (LL) Answer * 2 - (LL) N * M;
    std :: cout << Answer;    
    return 0;
}

 

posted @ 2017-12-19 10:14  ZlycerQan  阅读(179)  评论(0编辑  收藏  举报