杜教筛 与 数论函数(狄雷克卷积)

Posted 殇雪

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了杜教筛 与 数论函数(狄雷克卷积)相关的知识,希望对你有一定的参考价值。

   为了改变数论只会GCD的尴尬局面,我们来开一波数论:

     数论函数:

  • 数论函数是定义域在正整数的函数。
  • 积性函数: f(ab)=f(a)f(b),gcd(a,b)=1 ,完全积性函数: f(ab)=f(a)f(b) 。
  • 常见积性函数: φ(n) ,μ(n) (莫比乌斯函数), d(n) (因子个数), σ(n) (因子和)。
  • 单位函数 : e(n)=[n=1] 。
  • 常见完全积性函数: Idk(n)=n^k , 1(n)=Id0(n) , Id(n)=Id1(n) 。

     我们 有以下令人窒息的操作:

        (F*G)(x)=∑d|n F(d)*G(n/d)

    这种操作我们称之为狄雷克卷积。满足交换律,结合律,分配律以及 fe=f 。

    我们有:      d(n)=(11)(n)

                      σ(n)=(Id1)(n)

                      e=μ1

    都可以由积性函数的性质再套一个乘法分配率可以证出来。

    我们有两个重要的数论函数 φ(n) ,μ(n) 。

     φ(n),1到n里面与N互质的数的个数。有重要的等式:φ*1=id

     μ(n), 我们可以理解为1的逆元。 我们发现狄雷克卷积可以构成一个群,生成元是e ,1* μ=e,我们又知道*1又叫求该函数的狄雷克前缀和,那么再卷上μ就是原来的函数了。 

     我们设F=f*1,则 f=F* μ 

      那么我们就可以做一些简单的题目了:

      bzoj 3884 .这道题我们要一个结论:

      X^i=X^(i>φ(p)?i mod  φ(p)+φ(p):i) (mod p)

   特殊的 ,我们有 X^i=X^(i%(p-1)) (mod p) p为质数。

     我们还要知道φ(φ(p))<n/2,那么我们不妨迭代指数,最多迭代log p次指数为1,我们停止迭代。

    

#include<bits/stdc++.h>
using namespace std;
int sed(int &p){
    int ll=0;
    while (p%2==0) ll++,p>>=1; return ll;
}
int fai(int x){
     int i,re=x;  
    for(i=2;i*i<=x;i++)  
        if(x%i==0)  
        {  
            re/=i;re*=i-1;  
            while(x%i==0)  
                x/=i;  
        }  
    if(x^1) re/=x,re*=x-1;  
    return re;  
}
long long QP(long long x,int y,int p){
      long long re=1;  
    while(y)  
    {  
        if(y&1) (re*=x)%=p;  
        (x*=x)%=p; y>>=1;  
    }  
    return re; 
}
long long dos(int p){
    if (p==1) return 1;
    int k=sed(p), fi=fai(p);
    long long re=dos(fi);
    (re+=fi-k%fi)%=fi;  
  re=QP(2,re,p)%p;  
  return (re<<k);  
}
int main () {
    int t,p;
    scanf("%d",&t);
    while (t--) {
        scanf("%d",&p); 
        printf("%lld\\n",dos(p)%p);
    }
}
View Code

     bzoj 2186

     这某非就是传说中的gcd?

#include<bits/stdc++.h>
#define inff 10000000
#define N   10000090
int n,m,p,tot,tops,t,T;
int ans[N];int fa[N];
bool f[N];char c; int b,inf;
using namespace std;
inline char nc(){
    static char buf[100000],*p1=buf,*p2=buf;
    return p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++;
}
inline void read(int &x) {
    c=nc();
    b=1;
    for (; !(c>=\'0\' && c<=\'9\'); c=nc()) if (c==\'-\') b=-1;
    for (x=0; c>=\'0\' && c<=\'9\'; x=x*10+c-\'0\',c=nc());
    x*=b;
}
inline void exgcd(int a,int b,int &x,int &y)  
{  
    if (!b){x=1;y=0;return;}  
    exgcd(b,a%b,x,y);  
    int t=x;x=y;y=t-a/b*x;  
}  
inline int getinv(int a)  
{  
    int x=0,y=0;  
    exgcd(a,p,x,y);  
    return (x%p+p)%p;  
}  
void getfine(){
    ans[1]=1;t=0;
     for (int i=2;i<=inf;i++)
    {  
        if (!f[i])  
            ans[i]=(long long)1ll*ans[i-1]*(i-1)%p*getinv(i)%p,fa[++t]=i;  
        else ans[i]=ans[i-1];  
        for(int j=1;j<=t;j++) 
        {
            if((long long)fa[j]*i>inf)break;
            f[fa[j]*i]=1;
            if(i%fa[j]==0)break;
        }  
    }  
}
inline void write(int x)
{
     if(x<0) putchar(\'-\'),x=-x;
     if(x>9) write(x/10);
     putchar(x%10+\'0\');
}
int main (){
    //scanf("%d%d",&T,&p); 
  read(T); read(p);
    inf=min(p,inff);
    getfine();fa[0]=1;
    for (int i=1;i<=inf;i++) fa[i]=(long long)(1ll*fa[i-1]*i)%p;
    while (T--){
        //scanf("%d%d",&n,&m);
        read(n); read(m);
        int top=1ll*fa[n]%p*ans[m]%p;
        //printf("%d\\n",top);
        write(top); putchar(\'\\n\');
    }
}
View Code

     bzoj 2440

     二分答案,我们转为一个计数问题,我们统计有多少符合条件的数小于X?

     由容斥原理得 ans=∑ x/(i*i)*μ(i)

#include<bits/stdc++.h>
#define N 50000
#define ll long long 
using namespace std;
inline ll read()
{
    ll x=0,f=1;char ch=getchar();
    while(ch<\'0\'||ch>\'9\'){if(ch==\'-\')f=-1;ch=getchar();}
    while(ch>=\'0\'&&ch<=\'9\'){x=x*10+ch-\'0\';ch=getchar();}
    return x*f;
}
int T;
ll ans,K;
int tot;
int mu[50005],pri[50005];
bool mark[50005];
void getmu()
{
    mu[1]=1;
    for(int i=2;i<=N;i++)
    {
        if(!mark[i])pri[++tot]=i,mu[i]=-1;
        for(int j=1;j<=tot&&pri[j]*i<=N;j++)
        {
            mark[i*pri[j]]=1;
            if(i%pri[j]==0){mu[i*pri[j]]=0;break;}
            else mu[i*pri[j]]=-mu[i];
        }
    }
}
ll cal(int x)
{
    ll sum=0;
    int t=sqrt(x);
    for(int i=1;i<=t;i++)
        sum+=x/(i*i)*mu[i];
    return sum;
}
int main()
{
    getmu();
    T=read();
    while(T--)
    {
        K=read();
        ll l=K,r=1644934081;
        while(l<=r)
        {
            ll mid=(l+r)>>1;
            if(cal(mid)>=K)ans=mid,r=mid-1;
            else l=mid+1;
        }
        printf("%lld\\n",ans);
    }
    return 0;
}
View Code

   给出T组N和M,依次求出的值

   

    我们考虑反演:∑i∑j  (gcd=1)   =∑i∑j ∑d |gcd(i,j) μ(i)=∑ d μ(d) (n/d)*(m/d)

    我们发现其只有O(N^0.5)个整点,分块处理。

#include<bits/stdc++.h>
#define N 1000000
bool mark[N+5];
int prime[N+5];
int num;
long long sum[N+5];
int euler[N+5];
inline int Min(int a,int b){return a<b?a:b;}
void Euler()
{
    int i,j;
    num = 0;
    memset(euler,0,sizeof(euler));
    memset(prime,0,sizeof(prime));
    memset(mark,0,sizeof(mark));
    euler[1] = 1; // multiply function
    sum[1]=1;
    for(i=2;i<=N;i++)
    {
        if(!mark[i])
        {    
            prime[num++] = i;
            euler[i] = -1;
        }
        for(j=0;j<num;j++)
        {
            if(i*prime[j]>N){break;}
            mark[i*prime[j]] = 1;
            if(i%prime[j]==0)
            {
                euler[i*prime[j]] = 0;
                break;
            }
            else
            {
                euler[i*prime[j]] = -euler[i];
            }
        } sum[i]=sum[i-1]+euler[i];
    }
}
 
int main()
{
    int i;
    int M1,M2;
    Euler(); int t;
    scanf("%d",&t);
   while (t--){
    scanf("%d%d",&M1,&M2);
    long long ans = 0;
    int min = Min(M1,M2);
    /*for(i=1;i<=min;i++)
    {
        sum += euler[i]*(M1/i)*(M2/i);
    }*/
    for (int i=1,last;i<=min;i=last+1){
        last=Min(M1/(M1/i),M2/(M2/i));
        ans+=(sum[last]-sum[i-1])*(M1/i)*(M2/i);
        }
    printf("%lld\\n",ans);
        }
}
View Code

   

 

  给出T组N和M,求的值

 

  

    我们考虑反演:∑i∑j  (gcd=1)   =∑i∑j ∑d |gcd(i,j)φ (i)=∑ dφ(d) (n/d)*(m/d)

    我们发现其只有O(N^0.5)个整点,分块处理。

#include<bits/stdc++.h>
#define N 1000000
bool mark[N+5];
int prime[N+5];
int num;
long long sum[N+5];
int euler[N+5];
inline int Min(int a,int b){return a<b?a:b;}
void Euler()
{
    int i,j;
    num = 0;
    memset(euler,0,sizeof(euler));
    memset(prime,0,sizeof(prime));
    memset(mark,0,sizeof(mark));
    euler[1] = 1; // multiply function
    sum[1]=1;
    for(i=2;i<=N;i++)
    {
        if(!mark[i])
        {    
            prime[num++] = i;
            euler[i] = i-1;
        }
        for(j=0;j<num;j++)
        {
            if(i*prime[j]>N){break;}
            mark[i*prime[j]] = 1;
            if(i%prime[j]==0)
            {
                euler[i*prime[j]] = euler[i]*prime[j];
                break;
            }
            else
            {
                euler[i*prime[j]] = euler[i]*euler[prime[j]];
            }
        } sum[i]=sum[i-1]+euler[i];
    }
}
 
int main()
{
    int i;
    int M1,M2;
    Euler(); int t;
    scanf("%d",&t);
   while (t--){
    scanf("%d%d",&M1,&M2);
    long long ans = 0;
    int min = Min(M1,M2);
    /*for(i=1;i<=min;i++)
    {
        sum += euler[i]*(M1/i)*(M2/i);
    }*/
    for (int i=1,last;i<=min;i=last+1){
        last=Min(M1/(M1/i),M2/(M2/i));
        ans+=(long long)1ll*(sum[last]-sum[i-1])*(M1/i)*(M2/i);
        }
    printf("%lld\\n",ans);
        }
}
View Code

    那么我们就可以上杜教筛了。

    举个例子:这里  我们要在线性以下的时间搞定φ的前缀和。

    (⊙o⊙)… 我放弃了,不会打数学公式。 

     丢个链接吧   这里   ,  这里 

#include<bits/stdc++.h>
#define N 4011007
#define LL long long
#define mo 1000000007
#define fg  
#define int LL
using namespace std;
LL pim[N>>3],fi[N],tot,a[N],b[N],inv2=500000004,s=N-1007,n,SI,fis[N];
inline LL Mo(LL x){x%=mo;if (x<0) x+=mo;return x;}
LL tr(LL x) {if (x<=s) return a[x]; return b[n/x];} 
void put(LL x,LL y) {if (x<=s) a[x]=y; else b[n/x]=y;}
LL F(LL x) {
    if (x<SI) return fis[x];
    if (tr(x)) return tr(x);
    LL ans=Mo(x)*Mo(x+1)%mo*inv2%mo;  
    for (LL i=2,last;i<=x;i=last+1) {
        last=x/(x/i);
        ans=Mo(ans-F(x/i)*Mo(last-i+1)%mo);
    } put(x,ans);return ans;
}
void Pin(int MA){
    fis[1]=1;
    for (int i=2;i<MA;i++) {
        if (!fi[i]) pim[++tot]=i,fi[i]=i-1;
        for (int j=1;j<=tot&&pim[j]*i<MA;j++)
         if(i%pim[j]) fi[i*pim[j]]=fi[i]*(pim[j]-1);
         else {fi[i*pim[j]]=fi[i]*pim[j]; break;} 
        fis[i]=Mo(fis[i-1]+fi[i]);
    }
}
void Init() {
    SI=min((int)powl(n,0.666),(LL)N-12);
    Pin(SI);
}
signed main () {
    scanf("%lld",&n);
    Init();
    printf("%lld",F(n)); 
}
View Code

  再丢几道题目:

  莫比乌斯前缀 

#include<bits/stdc++.h>
#define LL long long
#define N 4000007
using namespace std;
map<LL,int> ma;
LL a,n;
int LS,fis[N],pim[N>>3],tot,fi[N],u[N];
bool p[N];
LL F(LL x){
    if (x<LS) return fis[x];
    if (ma.count(x)) return ma[x];
    LL ans=1;
    for (LL i=2,last;i<=x;i=last+1) {
        last=x/(x/i); ans=ans-(last-i+1)*F(x/i);
    } ma[x]=ans; return ans;
}
void LST(int LS){
    fis[1]=1;
    for (int i=2;i<LS;i++) {
        if (!p[i]) u[i]=-1,pim[++tot]=i;
        for (int j=1;j<=tot&&i*pim[j]<LS;j++) {
            p[i*pim[j]]=1; if (i%pim[j]) u[i*pim[j]]=-u[i]; else{ u[i*pim[j]]=0; break;}
        }
        fis[i]=(fis[i-1]+u[i]);
    }
}
以上是关于杜教筛 与  数论函数(狄雷克卷积)的主要内容,如果未能解决你的问题,请参考以下文章

数论学习笔记2之杜教筛初探(含例题练习)

杜教筛学习笔记

数论入门——莫比乌斯函数,欧拉函数,狄利克雷卷积,线性筛,莫比乌斯反演,杜教筛

杜教筛 & Powerful Number 筛

[积性函数杜教筛莫比乌斯函数入门]学习总结

杜教筛