• 数论题总结


      这几天做了几道微不足道的数论题,感觉做法都十分的高啊,我这种僵化的思想是很难有那样的切题知识水平的。然后做了几道题,感觉也有点熟悉数学的那一套理论了,虽然我还是太弱,但是有点东西还是要讲出来的嘛,一起谈笑风生,积累人生经验。闷声发大财虽然好,但是是不支持的。

      上面那句话看不懂就无视吧。

      那么对于数论题,我们应该如何下手呢??我总结了一些分析技巧和优化技巧,希望有用(希望考场推得出来)。

    题目分析:

    1.题目给的很直接了,让你求这个那个。

      以两道同年的NOI题目为例。

      1.荒岛野人 Savage

        反正不管怎么样,看完题目我就知道考什么了(这并不能掩饰我WA几次的事实)。一眼扩展欧几里得,数据规模也当然很支持。暴力枚举m,n2扩欧就能跑过,速度较西方记者而言也不遑多让。

    #include    <iostream>
    #include    <cstdio>
    #include    <cstdlib>
    #include    <algorithm>
    #include    <vector>
    #include    <cstring>
    #include    <queue>
    #define LL long long int
    #define ls (x << 1)
    #define rs (x << 1 | 1)
    using namespace std;
    const int N = 21;
    int n,m,c[N],p[N],l[N];
    inline int gi()
    {
      int x=0,res=1;char ch=getchar();
      while(ch>'9'||ch<'0'){if(ch=='-')res*=-1;ch=getchar();}
      while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
      return x*res;
    }
    inline int Abs(int x){return x>0?x:-x;}
    inline int gcd(int a,int b){return b?gcd(b,a%b):a;}
    inline void exgcd(int a,int b,int &x,int &y)
    {
      if(b==0){x=1;y=0;return;}
      exgcd(b,a%b,y,x);y-=a/b*x;
    }
    inline bool check(int i,int j,int cir)
    {
      int A=(p[i]-p[j]),B=cir,C=(c[j]-c[i]),X=0,Y=0;
      int d=gcd(A,B);if(C%d!=0)return true;
      exgcd(A,B,X,Y);B=Abs(B/d);
      X=((X/d*C)%B+B)%B;if(X==0)X+=B;
      return X>min(l[i],l[j]);
    }
    int main()
    {
      n=gi();
      for(int i=1;i<=n;++i)
        c[i]=gi(),p[i]=gi(),l[i]=gi(),m=max(m,c[i]);
      for(;;++m)
        {
          int flag=1;
          for(int i=1;i<=n;++i)
        	for(int j=i+1;j<=n;++j)
    	     if(!check(i,j,m))
    	        {flag=0;i=n+1;break;}
          if(flag)break;
        }
      printf("%d",m);
      return 0;
    }
    

     

     

      2.机器人m号 robot

           讲真我认为应该批判一番这个出题人,本来考场时间就不多,还花个10分钟看题,还要记住什么军人学者政客(噫,怎么这么像他)

       独立数就是欧拉函数嘛,至于按因子数分类什么的跟莫比乌斯函数关系并不(没)大(有)。然后求出军人政客后用欧拉函数的性质求出学者的个数就可以了。

       这题做法还是比较神的。欧拉函数不是是积性的嘛,它又是给你的质数因子,所以你利用积性+乘法结合律,就可以写出一个二维DP的式子。但有一个更加高的方法就是直接把答案带进去递推,相当于用那个DP式子的分奇偶前缀和直接算,数组都不用开。

    #include    <iostream>
    #include    <cstdio>
    #include    <cstdlib>
    #include    <algorithm>
    #include    <vector>
    #include    <cstring>
    #include    <queue>
    #define LL long long int
    #define ls (x << 1)
    #define rs (x << 1 | 1)
    using namespace std;
    const LL Mod = 10000;
    LL Ans1,Ans2,m,k;
    int gi()
    {
      int x=0,res=1;char ch=getchar();
      while(ch>'9'||ch<'0'){if(ch=='-')res*=-1;ch=getchar();}
      while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
      return x*res;
    }
    void pL(LL x)
    {
      if(x<0)putchar('-'),pL(-x);
      if(x<10)putchar(x+'0');
      else pL(x/10),putchar(x%10+'0');
    }
    void pc(){putchar('
    ');}
    LL gL()
    {
      LL x=0,res=1;char ch=getchar();
      while(ch>'9'||ch<'0'){if(ch=='-')res*=-1;ch=getchar();}
      while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
      return x*res;
    }
    LL Qpow(LL d,LL z)
    {
      LL ans=1;
      for(;z;z>>=1,d=d*d%Mod)if(z&1)ans=ans*d%Mod;
      return ans;
    }
    int main()
    {
      k=gL();m=1;
      for(LL i=1;i<=k;++i)
        {
          LL p=gL(),e=gL();
          m=m*Qpow(p,e)%Mod;
          if(p==2)continue;
          LL ans1=(Ans1+(Ans2+1)*(p-1))%Mod;
          LL ans2=(Ans2+Ans1*(p-1))%Mod;
          Ans1=ans1;Ans2=ans2;
        }
      pL(Ans2);pc();pL(Ans1);pc();pL(((m-Ans1-Ans2-1)%Mod+Mod)%Mod);
      return 0;
    }
    

     

     

      2.公式推导

      题目给了你一个公式,但是直接算是显然会T的,这个时候就要将公式等价化简。这种题目SDOI以前是有的,现在他们的Oier身经百战了,见的多了,这种问题就没怎么出现过了。但对于新手来说,对于对数论函数的高妙使用的熟悉还是有很大帮助的。

      1.Longge的问题

    你看,多么简单的一个问题,但显然暴力是跑不过去的,这点上不管是香港记者还是松爷也必须承认的。

    那么我们必须考虑优化(不是底层优化!)。注意到有贡献的只有gcd,且关于gcd有一个优美的性质:gcd(a/gcd(a,b),b/gcd(a,b))=1;

    就是说除了gcd之后两个数忽然就互质了,于是个数就是欧拉那么多个。

    于是答案就是sigma(d|n) d*phi(n/d);phi在线爆算就可以了。

    #include    <iostream>
    #include    <cstdio>
    #include    <cstdlib>
    #include    <algorithm>
    #include    <vector>
    #include    <cstring>
    #include    <queue>
    #define LL long long int
    #define ls (x << 1)
    #define rs (x << 1 | 1)
    using namespace std;
    LL n,Ans;
    LL gL()
    {
      LL x=0,res=1;char ch=getchar();
      while(ch>'9'||ch<'0'){if(ch=='-')res*=-1;ch=getchar();}
      while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
      return x*res;
    }
    LL get(LL x)
    {
      LL ans=x;
      for(LL i=2;i*i<=x;++i)
        if(x%i==0)
          {
        ans=ans/i*(i-1);
        while(x%i==0)x/=i;
          }
      if(x!=1)ans=ans/x*(x-1);
      return ans;
    }
    int main()
    {
      n=gL();
      for(LL i=1;i*i<=n;++i)
        if(n%i==0)
          {
        if(i*i==n)Ans+=i*get(i);
        else Ans+=(i*get(n/i)+(n/i)*get(i));
          }
      printf("%lld
    ",Ans);
      return 0;
    }
    

     

     

     

     

     

     

     

    2.GCD

    这问的问题啊,还是太简单,太幼稚。

    一个数对(x,y)的gcd为质数p,如果假设x>y,那么对于(x/p,y/p)而言答案就是很水的类似于上一题的操作了。

    对于所有的数,我们用前缀和优化一下就可以了,答案再乘以2加上1就可以了。

    #include    <iostream>
    #include    <cstdio>
    #include    <cstdlib>
    #include    <algorithm>
    #include    <vector>
    #include    <cstring>
    #include    <queue>
    #define LL long long int
    #define Inc i*Prime[j]
    using namespace std;
    const int N = 10010000;
    int n,Prime[N],tot,ph[N],vis[N];
    LL Q[N],Ans;
    int gi()
    {
      int x=0,res=1;char ch=getchar();
      while(ch>'9'||ch<'0'){if(ch=='-')res*=-1;ch=getchar();}
      while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
      return x*res;
    }
    LL gl()
    {
      LL x=0,res=1;char ch=getchar();
      while(ch>'9'||ch<'0'){if(ch=='-')res*=-1;ch=getchar();}
      while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
      return x*res;
    }
    void prepare()
    {
      ph[1]=1;
      for(int i=2;i<=n;++i)
        {
          if(!vis[i]){Prime[++tot]=i;ph[i]=i-1;}
          for(int j=1;j<=tot;++j)
        {
          if(Inc>N)break;vis[Inc]=1;
          if(i%Prime[j])ph[Inc]=ph[i]*ph[Prime[j]];
          else{ph[Inc]=ph[i]*Prime[j];break;}
        }
        }
      for(int i=2;i<=n;++i)
        Q[i]=Q[i-1]+(LL)ph[i];
    }
    int main()
    {
      n=gi();prepare();
      for(int i=1;i<=tot;++i)
        Ans+=(2*(Q[n/Prime[i]])+1);
      printf("%lld",Ans);
      return 0;
    }
    

     

     

     

     

     

      3.构建数学模型,巧妙转化

      这部分是最难的。就像数学原理人人都会,但数学题不见得人人都会做;谁都有过冲动,但有的人非要去肛坦克。一般碰到这种题目,不应及时下手,应该在草稿纸上理性分析,并且打好暴力验证你的思想。学长传授的人生经验是,推一个式子打一遍,不要在过程中出现偏差。

    1.仪仗队

    这道题简直就是莫比乌斯反演的弱化版(第一象限)的弱化版(n=m)的弱化版(n<=40000)好吗!真的不怕考场上有人打表吗?

    反正一个点能被看到就是这个点横纵坐标的gcd=1才可以!你再只看一个上或者下三角,就是欧拉函数!水的一笔。

    /**************************************************************
        Problem: 2190
        User: Ryze
        Language: C++
        Result: Accepted
        Time:24 ms
        Memory:1760 kb
    ****************************************************************/
     
    #include    <iostream>
    #include    <cstdio>
    #include    <cstdlib>
    #include    <algorithm>
    #include    <vector>
    #include    <cstring>
    #include    <queue>
    #define LL long long int
    #define Inc i*Prime[j]
    using namespace std;
    const int N = 40100;
    int n,vis[N],Prime[N],ph[N],tot,Ans;
    int gi()
    {
      int x=0,res=1;char ch=getchar();
      while(ch>'9'||ch<'0'){if(ch=='-')res*=-1;ch=getchar();}
      while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
      return x*res;
    }
    void pui(int x)
    {
      if(x<10)putchar(x+'0');
      else pui(x/10),putchar(x%10+'0');
    }
    void shai()
    {
      ph[1]=1;
      for(int i=2;i<=n;++i)
        {
          if(!vis[i]){Prime[++tot]=i;ph[i]=i-1;}
          for(int j=1;j<=tot;++j)
        {
          if(Inc>n)break;vis[Inc]=1;
          if(i%Prime[j])ph[Inc]=ph[i]*ph[Prime[j]];
          else {ph[Inc]=ph[i]*Prime[j];break;}
        }
          Ans+=ph[i];
        }Ans-=ph[n]-1;
    }     
    int main()
    {
      n=gi();shai();
      printf("%d",Ans*2+1);
      return 0;
    }
    

     

     

     

    2.古代猪文

      题面是很有趣的,充满着批判的意味,仿佛是要弄出一个大新闻,让人Excited!

      在PKU的学长跟我们讲这是板子合集,前提是你要看出来。

      题目显然求G的sigma{d|n}(一个组合数)次方。组合数之和做指数肯定是不支持的。因为模数是个质数,就把指数模一个phi(模数)=模数-1;又因为模数-1是个合数,且发现没有相同质因子,就用扩展Lucas来敲。然后再打一个快速幂就可以AC这道牛逼哄哄的题目惹。

    #include    <iostream>
    #include    <cstdio>
    #include    <cstdlib>
    #include    <algorithm>
    #include    <vector>
    #include    <cstring>
    #include    <queue>
    #define LL long long int
    #define ls (x << 1)
    #define rs (x << 1 | 1)
    using namespace std;
    const LL Mod = 999911659;
    const int N = 50010;
    LL n,g,M[]={0,2ll,3ll,4679ll,35617ll},Y[10];
    LL J[N],A[N],Z[10];
    void pL(LL x)
    {
      if(x<10)putchar(x+'0');
      else pL(x/10),putchar(x%10+'0');
    }
    LL gL()
    {
      LL x=0,res=1;char ch=getchar();
      while(ch>'9'||ch<'0'){if(ch=='-')res*=-1;ch=getchar();}
      while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
      return x*res;
    }
    void prepare(LL x)
    {
      A[1]=J[0]=J[1]=1;
      for(LL i=2;i<x;++i)
        J[i]=J[i-1]*i%x,A[i]=((-(x/i)*A[x%i])%x+x)%x;
    }
    LL C(LL N,LL M,LL P)
    {
      if(N<M)return 0ll;
      LL ans=J[N];
      ans*=A[J[M]];ans%=P;
      ans*=A[J[N-M]];ans%=P;
      return ans;
    }
    LL Lucas(LL N,LL M,LL P)
    {
      if(!M)return 1ll;
      if(N<P&&M<P)return C(N,M,P);
      LL c=C(N%P,M%P,P);if(!c)return 0ll;
      LL L=Lucas(N/P,M/P,P);return c*L%P;
    }
    LL get(LL mod)
    {
      LL ans=0;
      for(LL i=1;i*i<=n;++i)
        if(n%i==0)
          {
          ans+=Lucas(n,i,mod);ans%=mod;
          if(i*i!=n)ans+=Lucas(n,n/i,mod),ans%=mod;
          }
      return ans;
    }
    LL Qpow(LL d,LL z)
    {
      LL ans=1;
      for(;z;z>>=1,d=d*d%Mod)if(z&1)ans=ans*d%Mod;
      return ans;
    }
    int main()
    {
      n=gL();g=gL()%Mod;
      if(g==0){pL(0);return 0;}
      for(int t=1;t<=4;++t)
        {
          prepare(M[t]);
          Y[t]=get(M[t]);
          Z[t]=((Mod-1)/M[t])*A[((Mod-1)/M[t])%M[t]];
          Y[0]+=Y[t]*Z[t]%(Mod-1);Y[0]%=(Mod-1);
        }
      return pL(Qpow(g,Y[0])),0;
    }
    

     

     

    3.圆上的整点

      这题真是神了系列。

      首先你会想到a^2+b^2=r^2之枚举a看b。现在我们来想想优化。

      a^2+b^2=r^2;

      a^2=r^2-b^2;

      a^2=(r+b)(r-b);

      令d = gcd(右边);  // 神奇思想。左式为完全平方数,就在右边构建完全平方数。

      a^2=d^2*[(r+b)/d]*[(r-b)/d];

      因为右边是完全平方数,两中括号内的数互质,所以本身就是完全平方数。

      令其分别为p^2,q^2;

      则有: p^2+q^2=2r/d;

      所以你就可以直接枚举了。枚举d,再枚举p或者q就好。

    #include    <iostream>
    #include    <cstdio>
    #include    <cstdlib>
    #include    <algorithm>
    #include    <cstring>
    #include    <cmath>
    #define LL long long int
    #define ls (x << 1)
    #define rs (x << 1 | 1)
    #define fa (x >> 1)
    #define MID  LL mid=(l+r)>>1
    #define max(a,b) (a>b?a:b)
    #define min(a,b) (a<b?a:b)
    using namespace std;
    LL Ans,r,R,p,q,A,B,X,Y,d;
    LL gi()
    {
      LL x=0,res=1;char ch=getchar();
      while(ch>'9' || ch<'0'){if(ch=='-')res=-1;ch=getchar();}
      while(ch>='0'&&ch<='9')x=x*10+ch-48,ch=getchar();
      return x*res;
    }
    LL gcd(LL a,LL b){return b==0?a:gcd(b,a%b);}
    int main()
    {
      R=gi();r=2*R;
      for(d=1;d<=sqrt(r);++d)
        {
          if(r%d==0)
            {
    	  LL K1=r/d;K1/=2;K1=sqrt(K1);
    	  for(q=1;q<K1;++q)
                {
    	      p=sqrt(r/d-q*q);
    	      if(p*p+q*q==r/d)
                    {
    		  A=p*p;B=q*q;
    		  if(gcd(A,B)!=1 || A==B)continue;
    		  else Ans++;
                    }
                }
    	  LL K2=d;K2/=2;K2=sqrt(K2);
    	  for(q=1;q<=K2;++q)
                {
    	      p=sqrt(d-q*q);
    	      if(p*p+q*q==d)
                    {
    		  A=p*p;B=q*q;
    		  if(gcd(A,B)!=1 || A==B)continue;
    		  else Ans++;
                    }
                }
            }
        }
      cout<<4*Ans+4;
    }
    

     

     

    优化技巧

    1.数论分块。

      我们经常推公式推出来一个关于“取下整”的东西。其实这就是我们分块优化的突破口。

      打一个表,我们可以发现,[n/i]取下整这种东西的取值并不多,有很多连续的一段其实都是一个值。经过严谨的证明,大概有2√n个。

      这个时候公式就可能可以得到简化。我们用前缀和维护一下,在一段相同取值的块内把整块求出来,然后直接跳到下一个块去。

      公式是:

        初始化:l=1,r;

        分块操作:r=n/(n/l);

        下一块:l=r+1;

      这便是套路所在。有此分块,O(n)的复杂度就被缩成了O(sqrt(n));数论分块在莫比乌斯和数论中应用十分广泛,考点也很多。

    下面有一道经典的数论分块题目,涉及到分块后的不同操作,推荐一下:

    模积和

     

    这里放白字题解:

      在此我们先了解正整数意义下的模运算,叫做取余运算。

      n%p 可以用式子 n-(n/p)*p 来等效替代(仅限于整型)。

      所以原式可以化简,具体式子我就给一神犇学长的博客。他推的比我的清楚,而且里面有套路。

      所以接下来的就是喜闻乐见的各种分块。注意一定要一步一模。

    #include    <iostream>
    #include    <cstdio>
    #include    <cstdlib>
    #include    <algorithm>
    #include    <vector>
    #include    <cstring>
    #include    <queue>
    #define LL long long int
    #define ls (x << 1)
    #define rs (x << 1 | 1)
    using namespace std;
    const LL Mod = 19940417;
    LL n,m,Ans;
    LL gL()
    {
      LL x=0,res=1;char ch=getchar();
      while(ch>'9'||ch<'0'){if(ch=='-')res*=-1;ch=getchar();}
      while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
      return x*res;
    }
    inline LL sum(LL l,LL r){return (r-l+1)*(r+l)/2%Mod;}
    inline LL Sum(LL x){return x*(x+1)%Mod*(2*x+1)%Mod*3323403%Mod;}
    inline LL calc(LL A)
    {
      LL ret=0;
      for(LL l=1,r;l<=A;l=++r)
        {
          r=A/(A/l);
          ret+=(r-l+1)*A%Mod-(A/l)*sum(l,r)%Mod;
          ret=(ret%Mod+Mod)%Mod;
        }
      return ret;
    }
    LL cal(LL A)
    {
      LL res=0;
      for(int l=1,r;l<=n;l=++r)
        {
          r=min(n/(n/l),m/(m/l));
          LL ans1=n*m%Mod*(r-l+1)%Mod;
          LL size=((Sum(r)-Sum(l-1))%Mod+Mod)%Mod;
          LL ans2=(n/l)*(m/l)%Mod*size%Mod;
          LL ans3=n*(m/l)%Mod*sum(l,r)%Mod;
          LL ans4=m*(n/l)%Mod*sum(l,r)%Mod;
          res=((res+ans1+ans2-ans3-ans4)%Mod+Mod)%Mod;
        }
      return res;
    }
    int main()
    {
      n=gL();m=gL();if(n>m)swap(n,m);
      LL ans1=calc(n),ans2=calc(m);
      Ans=ans1*ans2%Mod;
      LL ans3=cal(n);Ans=(Ans-ans3)%Mod;
      printf("%lld
    ",(Ans+Mod)%Mod);
      return 0;
    }
    

     2.未完待续... ...

     

     

  • 相关阅读:
    MySQL-索引和磁盘操作的关系
    P6033 合并果子 加强版
    电子海图开发第三十二篇 , s57,s52标准电子海图,绘图指令的解析(共一百篇)
    电子海图开发第三十一篇 , s57,s52标准电子海图,面物标区域的图案填充,符号,边界(共一百篇)
    电子海图开发第三十篇 , s57,s52标准电子海图,面物标区域的图案填充(共一百篇)
    电子海图开发第二十九篇 , s57,s52标准电子海图,面物标对象中心图标的绘制(共一百篇)
    电子海图开发第二十八篇 , s57,s52标准电子海图,面物标对象的绘制(共一百篇)
    电子海图开发第二十七篇 , s57,s52标准电子海图,复杂矢量线型符号的渲染续(共一百篇)
    电子海图开发第二十六篇。s57,s52标准电子海图,复杂矢量线型符号的渲染(共一百篇)
    电子海图开发第二十五篇,s52显示标准,海图的符号绘制方法(共一百篇)
  • 原文地址:https://www.cnblogs.com/fenghaoran/p/6659103.html
Copyright © 2020-2023  润新知