zoukankan      html  css  js  c++  java
  • 杜教筛 与 数论函数(狄雷克卷积)

       为了改变数论只会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
    ",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
    ",top);
            write(top); putchar('
    ');
        }
    }
    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
    ",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
    ",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
    ",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]);
        }
    }
    void init() {
        LS=min((int)pow(n,0.6667),N-47);
        LST(LS);
    }
    int main () {
        scanf("%lld %lld",&a,&n);
        init();
        printf("%lld",F(n)-F(a-1));
    }
    View Code

     公约数前缀和

    #include<bits/stdc++.h>
    #define mo 1000000007
    #define N 5000007
    #define LL long long
    using namespace std;
    LL n,fis[N],pim[N>>3],fi[N],IS,inv2=500000004,tot,ans,a[N],b[N],s=N-1007;
    inline LL Mo(LL x){x%=mo; if (x<0) x+=mo; return x;}
    //map<LL,LL> Ma;
    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 fiz(LL x){
        if (x<=IS) return fis[x];
        if (tr(x)) return tr(x);
    //    printf("%d
    ",x);
        LL ans=x%mo*((x+1)%mo)%mo*inv2%mo;
        for (LL i=2,last;i<=x;i=last+1) {
            last=x/(x/i);
            ans=(ans-fiz(x/i)*
            ((last-i+1)%mo)%mo)%mo;
        }
        put(x,ans); return ans;
    }
    void P_pit(LL ma_l){
        fis[1]=1;
        for (int i=2;i<=ma_l;i++) {
            if (!fi[i]) pim[++tot]=i,fi[i]=i-1;
            for (int j=1;j<=tot&&i*pim[j]<=ma_l;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]=fi[i]+fis[i-1];
        }
    }
    void init() {
        IS=min(N-17,(int)powl(n,0.6666));
        P_pit(IS); 
        fiz(n);
    }
    int main () {
    //    freopen("a.in","r",stdin);
        scanf("%lld",&n);
        init();
        for (LL i=1,last;i<=n;i=last+1) {
            last=n/(n/i);
            ans=(ans+Mo(n/i)*Mo(n/i)*Mo(fiz(last)-fiz(i-1))%mo);
            ans=Mo(ans);
        }
        printf("%lld
    ",ans); return 0;
    }
    View Code

     来一发技巧。对于单点杜教筛的话,我们可以这样存取值

    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;}

    那么我们就不用map 或哈希了。

    以后再学洲阁筛吧,挖坑就跑。

  • 相关阅读:
    记录一次阻塞引发的系统超时
    2015年读书清单
    循序渐进的敏捷-每日例会
    循序渐进的敏捷-交叉测试
    对一次系统上线的思考-走出“舒适区”
    单点登录(SSO)系统的总结
    对一个同事项目的思考和总结
    关于福建出差的总结
    由错误处理引发的联想-防御式编程
    关于日志记录的总结
  • 原文地址:https://www.cnblogs.com/rrsb/p/8321489.html
Copyright © 2011-2022 走看看