04 线性质数筛 欧拉函数 莫比乌斯函数 狄利克雷卷积(待补)

线性素数筛

https://www.luogu.com.cn/problem/P3383

板子

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll maxn=1e8+1000;
int t,p[maxn],k,q;
bool vis[maxn];
void make_prime(ll n){
    fill(vis,vis+n,0);
    ll cnt=0;
    for(int i=2;i<=n;i++){
        if(!vis[i]) p[++cnt]=i;
        for(int j=1;p[j]*i<=n&&j<=cnt;j++){
            vis[p[j]*i]=1;
            if(i%p[j]==0) break;
        }
    }
}
int main(){
    //freopen("in.txt","r",stdin);
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    cin>>t>>q;
    make_prime(t);
    while(q--){
        cin>>k;
        cout<<p[k]<<endl;
    }
    return 0;
}

欧拉函数

定义:

 $\varphi \left( x \right) $即为小于$x$的所有与$x$互质的整数的个数。

朴素单个欧拉函数值求法:

ll euler(ll n){
    ll res=n;
    for(ll i=2;i*i<=n;i++){
        if(n%i==0){
            res-=res/i;
            while(n%i==0)
                n/=i;
        }
    }
    if(n>1) res-=res/n;
    return res;
}

每次遇到可以整除$n$的数就从答案中减去所有该数的倍数,并对$n$进行调整。考虑到当$n$为质数时,整体的复杂度为$O\left( \sqrt{n} \right) $。

相关定理:

 1.当$p$为质数时,$\varphi \left( p \right) =p-1$,显然。

 2.若$p$为质数,且$n=p^k$,则$\varphi \left( n \right) =p^k-p^{k-1}$。

 3.欧拉函数为积性函数(我还不知道积性函数是什么),特殊的当$n$为奇数时,$\varphi \left( 2n \right) =\varphi \left( n \right) $。

 4.当$n>2$时,$\varphi(n)$必为偶数。

 5.比$n$小的所有的数的和为$n\cdot \varphi \left( n \right) /2$。

 6.若$a$与$n$互质,则有$a^{\varphi \left( n \right)}\equiv 1\left( \text{mod} n \right) \,\,$,$a^{b\,\,\left( \text{mod} \varphi \left( n \right) \right)}\equiv 1\left( \text{mod} n \right) \,\,$。

例题:

 P5091 【模板】扩展欧拉定理

 其他没啥就是这个$b$在读入的时候要按照字符读入然后取模,复杂度为$O\left( \sqrt{m}+\log  b \right) $,大概就是$1e7$的样子。

 TM wa了老半天才知道定理是有使用条件的,最终的定理长这样:

$$a^b=\left\{ \begin{array}{c}
a^b\,\, ,b<\varphi \left( m \right)\\
a^{b\,\,\text{mod} \varphi \left( m \right) +\varphi \left( m \right)}\,\,,b\ge \varphi \left( m \right)\\
\end{array} \right. $$

且此时有$gcd\left( a,m \right) \ne 1$

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll maxn=2e5+1000;
ll a,b,mod;
ll euler(ll n){
    ll res=n;
    for(ll i=2;i*i<=n;i++){
        if(n%i==0){
            res-=res/i;
            while(n%i==0)
                n/=i;
        }
    }
    if(n>1) res-=res/n;
    return res;
}
inline ll read(ll mod){
    ll x=0;char c=getchar();
    int yes=0;
    while (c<'0'||c>'9') c=getchar();
    while (c>='0'&&c<='9'){
        x=x*10+c-'0';
        if(x>=mod) x=x%mod,yes=1;
        c=getchar();
    }
    if(yes) return x;
    else return -x;
}
ll qpow(ll a,ll k){
    ll res=1,x=a;
    while(k){
        if(k&1){
            res=x*res%mod;   
        }
        x=x*x%mod;
        k>>=1;
    }
    return res;
}
int main(){
   // freopen("in.txt","r",stdin);
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    a=-read(1e9+100),mod=-read(1e9+100);
    ll phi=euler(mod);
    b=read(phi);
    if(a%mod==0) cout<<0<<endl;
    else{
        if(b>=0) 
            cout<<qpow(a,b+phi)%mod<<endl;
        else 
            cout<<qpow(a,-b)%mod<<endl;
    }
    return 0;
}

 P2158 [SDOI2008]仪仗队

实际上就是求出$1\sim n$内所有互质的点的个数$num$,然后答案就等于$ans=num+2-1$,加$2$是因为$(0,1),(1,0)$未算入,减一是因为$(1,1)$重复计算了。

所以有$ans=2 \sum_{i=0}^{n-1}{\varphi \left( i \right)}+1$。那么就需要快速的求出$\varphi \left( 1 \right) \sim \varphi \left( n \right) $,显然按照之前的求法是不好的,所以这里要用到线性筛法求出,复杂度为$O(n)$废话不然为什么叫线性筛。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll maxn=2e5+1000;
ll vis[maxn],p[maxn],phi[maxn];
ll euler(ll n){//朴素单个欧拉函数 O(sqrt(n)) 
    ll res=n;
    for(ll i=2;i*i<=n;i++){
        if(n%i==0){
            res-=res/i;
            while(n%i==0)
                n/=i;
        }
    }
    if(n>1) res-=res/n;
    return res;
}
void neuler(ll n){//得到1~n-1所有的欧拉函数 O(n)
    ll cnt=0;
    phi[1]=1;
    for(int i=2;i<n;i++){
        if(!vis[i]){
            p[cnt++]=i;
            phi[i]=i-1;
        }
        for(int j=0;j<cnt&&i*p[j]<n;j++){
            vis[i*p[j]]=1;
            if(i%p[j]==0){
                phi[i*p[j]]=phi[i]*p[j];
                break;
            }
            phi[i*p[j]]=phi[i]*(p[j]-1);
        }
    }
}
int main(){
    //freopen("in.txt","r",stdin);
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    ll  n;
    cin>>n;
    neuler(n+1);
    ll res=0;
    if(n==1){
        cout<<0<<endl;
        return 0;
    }
    for(int i=0;i<n;i++) res+=phi[i]; 
    cout<<2*res+1<<endl;
    return 0;
}

可以看到,其实基本的代码结构是和原本的质数筛相同的。基本上有三点不同,比如第六行的对$\varphi (i)$的赋值和第十一行和十四行的递推。从此也就可以看出线性筛的适用范围。就是当$n$和$m$互质时有:$f\left( n\cdot m \right) =f\left( n \right) \cdot f\left( m \right) $,这也就是积性函数的定义。这个东西太高级了,现在就先当黑箱子用吧,待补(咕咕)。

莫比乌斯函数

定义

$$\mu \left( x \right) =\begin{cases}
\,\, 1 \text{,}x=1\\
-1^k\text{,}x\text{无平方因数且}x=p_1p_2p_3...p_k\\
\,\, 0 \text{,}x\text{有平方因数}\\
\end{cases}$$换句人话就是说如果可以表示为奇数个不同素数的乘积就是-1,如果可以表示为偶数个不同素数的乘积就是1,如果表示出来有相同素数就是0。

单个莫比乌斯函数的朴素算法

 1 int mob(ll n){//单个函数的算法 O(sqrt(n))
 2     ll cnt=0,temp=0;
 3     for(ll i=2;i*i<=n;i++){
 4         temp=0;
 5         if(n%i==0){
 6             while(n%i==0) temp++,n/=i;
 7             if(temp>1) return 0;
 8             cnt++;
 9         }
10     }
11     if(n!=1) cnt++;
12     return (cnt&1)?-1:1;
13 }

相关定理

1.

$$\sum_{d\mid n}{\mu \left( d \right)}=\left\{ \begin{array}{c}
1 \left( n=1 \right)\\
0 \left( n\ne 1 \right)\\
\end{array} \right. $$

当$n=1$时是显然的;当$n \ne 1$时,$d$相当于从$n$的素数乘积表示中选取,故有:

$$\sum_{d\mid n}{\mu \left( d \right)}=\sum_{i=0}^k{\left( -1 \right) ^iC_{k}^{i}}$$

由组合的性质可知后者等于$0$。嗷看了半天才知道,这个定理可以将$n=1$的条件判断变成左边的式子,这样在某些时候就可以化简表达式。

2.

$$\sum_{d\mid n}^{}{\cfrac{\mu \left( d \right)}{d}}=\cfrac{\varphi \left( n \right)}{n}$$

证明略,要用到一些我不会的东西

线性筛法

显然当$n,m$互质时,由于二者没有共同的因数,所以乘积的表示中不会有同项,由定义可知此时有:

$$\mu \left( n\cdot m \right) =\mu \left( n \right) \cdot \mu \left( m \right) $$

故莫比乌斯函数为积性函数,可以用线性筛法求出。

 1 int u[maxn],p[maxn];
 2 bool vis[maxn];
 3 void nmob(ll n){
 4     u[1]=1;
 5     ll cnt=0;
 6     for(int i=2;i<=n;i++){
 7         if(!vis[i])p[++cnt]=i,u[i]=-1;
 8         for(int j=1;j<=cnt&&i*p[j]<=n;j++){
 9             vis[i*p[j]]=1;
10             if(i%p[j]) u[i*p[j]]=-u[i];
11             else break;
12         }
13     }
14 }

例题

HDOJ-2841

类似于上例中的仪仗队但是对$x,y$的限制不同了,有俩种方法。第一种是利用容斥思想将$y$分解然后对于每个$n$除去相应的因数的个数,但是有的数字会被重复删去,所以要将删去的再次加回来,而这个系数正是该数对应的莫比乌斯函数值;重点看一下第二种方法,利用上面说的等价判断有:

$$\sum_{i=1}^n{\sum_{j=1}^n{\left[ gcd\left( i,j \right) ==1 \right]}}
\\
=\sum_{i=1}^n{\sum_{j=1}^n{\sum_{d\mid gcd\left( i,j \right)}^{}{\mu \left( d \right)}}}
\\
=\sum_{d=1}^{\min \left( n,m \right)}{\mu \left( d \right) \sum_{i=1}^{n/d}{1}\sum_{j=1}^{m/d}{1}}
\\
=\sum_{d=1}^{\min \left( n,m \right)}{\mu \left( d \right) \cdot \frac{n}{d}\cdot \frac{m}{d}}$$

哇,说实话这个变换真的秀,这要我就不可能想得到啊。第一步用的就是定理,但是第二步就很巧妙。因为$d\mid gcd\left( i,j \right) $中要让$d$存在,那么$gcd\left( i,j \right) $必须要大于等$d$所以每一个$d$的贡献就是$$\mu \left( d \right) \sum_{i=1}^{n/d}{1}\sum_{j=1}^{m/d}{1}$$后面之所以是从$1$到$n/d$,就是看$n$最多可以被$d$的几倍整除,然后前面之所以是$\min \left( n,m \right) $是因为$d$最大也只能到这个值且此时$n,m$有倍数关系。然后这个问题就变成了线性问题,结合线性筛就直接就是裸题了,相比于第一种方法代码量要小很多。果然数学越好代码越少。

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 typedef long long ll;
 4 const ll maxn=2e5+1000;
 5 int mob(ll n){//单个函数的算法 O(sqrt(n))
 6     ll cnt=0,temp=0;
 7     for(ll i=2;i*i<=n;i++){
 8         temp=0;
 9         if(n%i==0){
10             while(n%i==0) temp++,n/=i;
11             if(temp>1) return 0;
12             cnt++;
13         }
14     }
15     if(n!=1) cnt++;
16     return (cnt&1)?-1:1;
17 }
18 int u[maxn],p[maxn];
19 bool vis[maxn];
20 void nmob(ll n){
21     u[1]=1;
22     ll cnt=0;
23     for(int i=2;i<=n;i++){
24         if(!vis[i])p[++cnt]=i,u[i]=-1;
25         for(int j=1;j<=cnt&&i*p[j]<=n;j++){
26             vis[i*p[j]]=1;
27             if(i%p[j]) u[i*p[j]]=-u[i];
28             else break;
29         }
30     }
31 }
32 ll t,n,m;
33 int main(){
34     //freopen("in.txt","r",stdin);
35     ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
36     cin>>t;
37     nmob(1e5+100);
38     while(t--){
39         cin>>n>>m;
40         ll xiao=min(n,m),res=0;
41         for(int d=1;d<=xiao;d++){
42             ll a=n/d,b=m/d;
43             res+=a*b*u[d];
44         }
45         cout<<res<<endl;
46     }
47     return 0;
48 }

原文地址:https://www.cnblogs.com/Zabreture/p/13419558.html