数论函数 笔记

版权声明:博主是个蒟蒻,希望大家支持,如果要转发就转发吧,把我链接挂上即可。 https://blog.csdn.net/LightningUZ/article/details/89004951

介绍

数论函数就是丢进去一个整数,丢出来一个整数的函数。很简单吧?(原因是我讲的有点俗。。。)

几个定义:
积性函数:一个数论函数 f f 满足:对于任意互质的p,q, f ( p q ) = f ( p ) f ( q ) f(pq)=f(p)*f(q)
完全积性函数:同上,且p,q不互质时也满足

狄利克雷卷积

两个函数 f f g g ,它们的卷积就是因数对的值相乘再相加。如 ( f g ) ( 8 ) = f ( 1 ) g ( 8 ) + f ( 2 ) g ( 4 ) + f ( 4 ) g ( 2 ) + f ( 8 ) g ( 1 ) (f*g)(8)=f(1)g(8)+f(2)g(4)+f(4)g(2)+f(8)g(1) 。写成式子的形式就是 ( f g ) ( x ) = d x f ( d ) g ( x d ) (f*g)(x)=\sum\limits_{d|x}f(d)*g(\frac{x}{d})
显然,我们发现,这是对称的,所以狄利克雷卷积满足交换律和结合律。

几个常见的函数

1.epsilon( ϵ \epsilon ):单位函数,类似乘法中的"1",对任意数论函数 f f 满足 ϵ f = f \epsilon*f=f

2. I I :恒等函数,传什么进去都是返回1

3. i d id :传多少返回多少,即 i d ( n ) = n id(n)=n

4.(开始复杂了)phi( ϕ \phi ),欧拉函数,返回1~n中有多少和n互质。

5.(主角) mu( μ \mu ),莫比乌斯函数,返回

若n=1,返回1,否则

若n有一个因数是个不为1的完全平方数 ,则 μ ( n ) = 0 \mu(n)=0

否则,不难发现,n分解质因数后应该每一个指数都是1。此时 μ ( n ) = ( 1 ) k \mu(n)=(-1)^k ,其中k是n的质因子个数

举(几)个栗子:

μ ( 18 ) = 0 ( 9 18 9 = 3 2 ) \mu(18)=0(9|18\text{且}9=3^2)

μ ( 42 ) = 1 ( 42 = 2 3 7 , k = 3 ) \mu(42)=-1(42=2*3*7,k=3)

μ ( 210 ) = 1 ( 210 = 2 3 5 7 k = 4 ) \mu(210)=1(210=2*3*5*7,k=4)

上面几个函数都是积性函数

讲反演之前:

线性筛mu

mu是积性函数,和质数筛类似,时间复杂度也是O(n)。
代码:

#include<bits/stdc++.h>
#define int long long
#define N 1001000
using namespace std;

int primes[N];bool notp[N];
int mu[N];
void Init()
{
    int cnt=0;//当前多少质数
    int n=1000000;//规模(如果没有空间限制,珂以到1e7)
    
    notp[1]=mu[1]=1;//边界
    
    for(int i=2;i<=n;i++)
    {
        if (!notp[i])//如果i是质数
        {
            primes[++cnt]=i;//记录
            mu[i]=-1;//显然(因为此时k=1)
        }
        for(int j=2;j<=cnt and i*primes[j]<=n;j++)
        {
            int u=primes[j];//临时取出(为了代码更短)
            
            notp[i*u]=1;//i*u显然不会是质数
            
            if (i%u==0)
            {
                mu[i*u]=0;
                //此时因为i是u的倍数,所以i*u中至少包含两个u,即i*u是u*u的倍数,此时显然mu值为0
                break;
            }
            else
            {
                mu[i*u]=-mu[i];//即mu[i*u]=mu[i]*mu[u],由于mu[u]=-1,所以也珂以写成这个样子
            }
        }
    }
}

整除分块

如何快速求 i = 1 n n i \sum\limits_{i=1}^{n}\lfloor\frac{n}{i}\rfloor ?
打表找一下规律。把n设置成30,看看每个n/i的值。
结果:

30 15 10 7 6 5 4 3 3 3 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1

后面好长一段1啊!2,3也有一段,到4就是一个一个了。但是会不会这只是特殊情况呢?

把1~100打出来看看吧。
结果:

100 50 33 25 20 16 14 12 11 10 9 8 7 7 6 6 5 5 5 5 4 4 4 4 4 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1

一看:WA!好多一样啊!
找一下规律:如果当前块的起点是l,那么这个块的终点就是n/(n/l)。这个子问题的代码如下(也可以作为整除分块的模板):

#include<bits/stdc++.h>
#define ll long long
using namespace std;
int main()
{
    ll n;
    scanf("%lld",&n);

    int ans=0;
    for(ll l=1,r;l<=n;l=r+1)
    {
        r=n/(n/l);//获取右边
        ans+=(n/l)*(r-l+1);//优化:不用一个一个加n/l,而是*(r-l+1),加快了很多
    }
    printf("%lld\n",ans);
}

复杂度是 O ( n ) O(\sqrt{n})

莫比乌斯反演

其实不用背那么多式子的吧。。。我觉得只要记得两个就够了。

μ I = ϵ \mu*I=\epsilon

ϕ I = i d \phi*I=id

(第1个更加常用,第二个也会有,也要了解,不过下面没有第二个式子的用法介绍)

如果你不信这两个个式子万能,请看题:

洛谷P3455 [POI2007]ZAP-Queries
(这个题目十分经典,珂以说是莫比乌斯反演的模板题)
(我没有把它写到做题笔记里面,原因是这个是模板,要放在学习笔记里面讲)
(别的题目可能会放在做题笔记里面)

题意概括:求

i = 1 n j = 1 m [ g c d ( i , j ) = d ] \sum\limits_{i=1}^{n}\sum\limits_{j=1}^{m}[gcd(i,j)=d]

(后为了方便设 g = g c d ( i , j ) g=gcd(i,j) )

此处顺便讲一下如何对带有sigma的是做变换。

首先,不妨令n<m(为什么不妨?原因是n,m对称)

我们枚举 i d i*d j d j*d ,显然 i d i*d j d j*d g c d gcd 肯定 &gt; = d &gt;=d ,如果i和j互质的话,就 = d =d 了。
所以珂以换成这样的枚举:

i d = 1 n j d = 1 m [ g = 1 ] \sum\limits_{i*d=1}^{n}\sum\limits_{j*d=1}^{m}[g=1]

这就相当于i,j是在枚举d的倍数。我们发现,d的倍数在1~n中应该有 n d \lfloor\frac{n}{d}\rfloor 个,m中同理。原式化为:

i = 1 n d j = 1 m d [ g = 1 ] \sum\limits_{i=1}^{\lfloor\frac{n}{d}\rfloor}\sum\limits_{j=1}^{\lfloor\frac{m}{d}\rfloor}[g=1]

然后,那个 g = 1 g=1 珂以换成 ϵ ( g ) \epsilon(g) (根据 ϵ \epsilon 的定义得),然后再换一下 ϵ \epsilon ,原式化为

i = 1 n d j = 1 m d q g μ ( q ) \sum\limits_{i=1}^{\lfloor\frac{n}{d}\rfloor}\sum\limits_{j=1}^{\lfloor\frac{m}{d}\rfloor}\sum\limits_{q|g}\mu(q)

接下来是思维难点:我们如何把q放到外面枚举?
(很多博客都没讲,害的蒟蒻理解了1个月才搞懂)
(如果您像博主一样也是个蒟蒻,请先背下来式子,然后反复做这一类的题目,有时间手动模拟一下这个换枚举的过程,慢慢就懂了)
我们珂以在外面先枚举上q,然后过会在尻♂虑哪些i,j会算到q。显然,满足 q g q|g 的一对i,j会算到一次q。则原式化为:

q = 1 n d i = 1 n d j = 1 m d [ q g ] μ ( q ) \sum\limits_{q=1}^{\lfloor\frac{n}{d}\rfloor}\sum\limits_{i=1}^{\lfloor\frac{n}{d}\rfloor}\sum\limits_{j=1}^{\lfloor\frac{m}{d}\rfloor}[q|g]\mu(q)

我们按刚刚类似的方法,枚举g的倍数,珂以得到:

q = 1 n d i = 1 n d q j = 1 m d q [ 1 g ] μ ( q ) \sum\limits_{q=1}^{\lfloor\frac{n}{d}\rfloor}\sum\limits_{i=1}^{\lfloor\frac{n}{dq}\rfloor}\sum\limits_{j=1}^{\lfloor\frac{m}{dq}\rfloor}[1|g]\mu(q)

发现对于任意的g都满足 [ 1 g ] [1|g] ,所以这个珂以省略不写

又由于 μ ( q ) \mu(q) 和i,j没有关系,所以珂以提到前面去。原式化为:

q = 1 n d μ ( q ) i = 1 n d q j = 1 m d q 1 = q = 1 n d μ ( q ) n d q m d q \sum\limits_{q=1}^{\lfloor\frac{n}{d}\rfloor}\mu(q)\sum\limits_{i=1}^{\lfloor\frac{n}{dq}\rfloor}\sum\limits_{j=1}^{\lfloor\frac{m}{dq}\rfloor}1=\sum\limits_{q=1}^{\lfloor\frac{n}{d}\rfloor}\mu(q)\lfloor\frac{n}{dq}\rfloor\lfloor\frac{m}{dq}\rfloor

到这里发现珂以整除分块, O ( n ) O(\sqrt{n}) 一遍过。

代码:

//有一些东西上面没有讲,代码里面会有简单的注释,如果理解不了。。。那就背下来,再多看几遍
#include<bits/stdc++.h>
#define int long long
#define N 1001000//没有这么大,但是习惯开这么大(2333)
using namespace std;

int primes[N];bool notp[N];
int mu[N];
void Init()
{
	//筛mu
    int cnt=0;
    int n=1000000;

    notp[1]=1;
    mu[1]=1;

    for(int i=2;i<=n;i++)
    {
        if (!notp[i])
        {
            primes[++cnt]=i;
            mu[i]=-1;
        }
        for(int j=1;j<=cnt and i*primes[j]<=n;j++)
        {
            int u=primes[j];

            notp[i*u]=1;

            if (i%u==0)
            {
                mu[i*u]=0;
                break;
            }
            else
            {
                mu[i*u]=-mu[i];
            }
        }
    }

	//这边记录前缀和,方便后面整除分块
    for(int i=1;i<=n;i++)
    {
        mu[i]+=mu[i-1];
    }
}
int n,m,d;
void Input()
{
    scanf("%lld%lld%lld",&n,&m,&d);
}

void Solve()
{
    if (n>m) swap(n,m);//不妨令n<=m
    int ans=0;
    for(int l=1,r;l<=n;l=r+1)
    {
        r=min(n/(n/l),m/(m/l));//两个同时分块一定要取最小(自己画图找规律)
        ans+=(n/(d*l))*(m/(d*l))*(mu[r]-mu[l-1]);//只要把n/dl*m/dl优化成*(mu[r]-mu[l-1]),即l~r的mu值和,就不用一个一个加了。
    }
    printf("%lld\n",ans);
}

main()
{
    Init();//预处理
    int T;scanf("%lld",&T);
    while(T-->0)//这个相当于T--,T-->0就是为了好看
    {
        Input();
        Solve();
    }
    return 0;
}

结束语

如果您看懂了刚刚的胡扯,那您应该已经入门莫比乌斯反演了,也对数论函数和sigma的变换有了一定的了解。当然,后续还会在做题笔记中也会提到跟复杂的变换,做好准备了么?

猜你喜欢

转载自blog.csdn.net/LightningUZ/article/details/89004951