数论复习笔记

来源:互联网 发布:js 屏幕手写板 编辑:程序博客网 时间:2024/06/08 15:26

持续更新中……

1. 线性筛O(N)

基础的线性筛模板。

#include<iostream>#include<cstdio>#include<cstdlib>using namespace std;const int maxn=1000005;int prime[maxn];bool notprime[maxn];int n,cnt=0;void shaiprime(){    for(int i=2;i<=n;i++)    {        if(!notprime[i])            prime[++cnt]=i;        for(int j=1;j<=cnt;j++)        {            if(prime[j]*i>n) break;            notprime[prime[j]*i]=true;            if(i%prime[j]==0) break;  //保证每个数只访问一次        }    }   }int main(){    scanf("%d",&n);    notprime[1]=true;    shaiprime();    for(int i=1;i<=cnt;i++)        printf("%d ",prime[i]);    return 0;} 

2 . 线性筛求欧拉函数

欧拉函数用希腊字母φ表示,φ(N)表示N的欧拉函数。对φ(N)的值,我们可以通俗地理解为小于N且与N互质的数的个数(包含1)。欧拉函数的积性即:若m,n互质,则φ(mn)=φ(m)*φ(n);
欧拉函数的值:
设 n = p1^a1 ∗ p2^2 ∗ …… ∗ pk^ak
那么 ϕ(n) = n ∗ (1 − 1/p1 ) ∗ (1 − 2/p2 ) ∗ …… ∗ (1 − 1/pk )
phi【n】表示的是在[1,N]中与N互质的正整数个数,当一个数n是素数时,因为素数p除了1以外的因子只有p,所以与 p 互素的个数是 p - 1个,显然phi【n】=n-1;
关键在如下结论:
设P是素数, 若p是x的约数,则E(x*p)=E(x)p. 若p不是x的约数,则E(x*p)=E(x)*E(p)=E(x)(p-1).
证明:(来自http://blog.csdn.net/nk_test/article/details/46242401,只摘取部分)
E(x)表示比x小的且与x互质的正整数的个数。
* E(p^k)=p^k-p^(k-1)=(p-1)*P^(k-1)
证:令n=p^k,小于n的正整数数共有n-1即(p^k-1)个,其中与p不质的数共[p^(k-1)-1]个(分别为1*p,2*p,3*p…p(p^(k-1)-1))。所以E(p^k)=(p^k-1)-(p^(k-1)-1)=p^k-p^(k-1).得证。
所以
E(p^k) =(p-1)*p^(k-1)=(p-1)*p^(k-2)*p
E(p^(k-1))=(p-1)*p^(k-2)
->当k>1时,E(p^k)=E(p* p^(k-1))=E(p^(k-1))*p.
(当k=1时,E(p)=p-1.)
由上式: 设P是素数,
若p是x的约数,则E(x*p)=E(x) * p.
若p不是x的约数,则E(x* p)=E(x)* E(p)=E(x)*(p-1).

代码如下:

#include<iostream>#include<cstdio>#include<cstdlib>using namespace std;const int maxn=200005;int prime[maxn],phi[maxn];bool notprime[maxn];int main(){    int n,cnt=0;    scanf("%d",&n);    notprime[1]=true;    phi[1]=1;    for(int i=2;i<=n;i++)    {        if(!notprime[i])        {            prime[++cnt]=i;            phi[i]=i-1;        }        for(int j=1;j<=cnt;j++)        {            if(prime[j]*i>n) break;            notprime[prime[j]*i]=true;            if(i%prime[j]==0)            {                phi[i*prime[j]]=prime[j]*phi[i];                break;            }            else                phi[i*prime[j]]=(prime[j]-1)*phi[i];        }    }} 

3 . exgcd(扩展欧几里得)

即为求ax+by=gcd(a,b) 满足条件的额一组解(x,y)。这里给出中间式子的证明。
证明:
已知gcd(a,b)=gcd(b,a%b)
a%b=a-a/b*b .这里的a/b 默认为a/b下取整。
a * x+b* y=gcd(a,b)=gcd(b,a%b)
= b * x1 + (a - (a/b) * b) * y1
= b * x1 + a * y1–(a/b) * b * y1
= a * y1 + b * (x1–a/b * y1)
两式一一对应
∴ x = y1
y = x1 – a/b * y1

补充1:exgcd已知特解 x0,y0求通解通式
x=xo+b/gcd(a,b)*t;
y=yo-a/gcd(a,b)*t; 注:t为任意整数
证明:
相当于加了一个(a * b)/d,又在后面减了一个(a * b)/d。
已知ax0+by0=gcd(a,b)
则ax0+by0+a*b/gcd(a,b)*t-a*b/gcd(a,b)*t=gcd(a,b);
整理得 a(x0+b/gcd(a,b)*t)+b(yo-a/gcd(a,b)*t)=gcd(a,b);
一一对应
则x=x0+b/gcd(a,b)*t,y=y0+a/gcd(a,b)*t
因为t为任意整数,所以正负随意。

补充2:求最小正整数解:x=(x%b+b)%b。道理显然,这里不给出证明

代码如下:

#include<iostream>#include<cstdio>#include<cstdlib>using namespace std;int exgcd(int a,int b,int &x,int &y){    if(b==0)    {        x=1,y=0;        return a;    }    int d=exgcd(b,a%b,x,y);    int tmp=x;    x=y;    y=tmp-a/b*y;    return d;}int main(){    int a,b,x,y,d;    scanf("%d%d",&a,&b);    d=exgcd(a,b,x,y);    cout<<d<<" "<<x<<" "<<y<<endl;    return 0;}

4 .求解最小的同时满足n个模等式的值

法一:大数翻倍法
思路如下:
对于n个模等式
n%m1=a1
n%m2=a2
n%m3=a3
……
n%mk=ak
例如:如果有两个模等式,则首先保证n%m1=a1,然后
while(n%m2≠a2)
n+=m1(保证n%m1=a1依旧成立)
while(n%m3≠a3)
n+=lcm(m1,m2)(保证n%m1=a1,n%m2=a2依旧成立)
∴对于while(n%m2≠a2) n+=m1;
上式也可等同于 while(n%m1≠a1) n+=m2;
因此我们可以选择m1,m2中较小的来加(减少加的次数,取min)
复杂度大约为(m1+m2+……+mn)-max(m1,m2,……,mn)≈根号x

但是这个有个bug,就是对于较大的质数,如两个1e9+7,复杂度贼高,往往会T掉,一般的数据都是可行的。

代码如下:

#include<iostream>#include<cstdio>#include<cstdlib>using namespace std;const int maxn=200005;int a[maxn],mo[maxn];int gcd(int a,int b){    if(b==0) return a;    return gcd(b,a%b);}int lcm(int a,int b){    return a/gcd(a,b)*b;}int main(){    int n;    scanf("%d",&n);    for(int i=1;i<=n;i++)        scanf("%d%d",&a[i],mo[i]);    int ans=0,nowmo=1;    for(int i=1;i<=n;i++)    {        int oth=a[i],othmo=mo[i];        if(othmo>nowmo)        {            swap(ans,oth);            swap(nowmo,othmo);        }        while(ans%othmo!=oth)            ans+=nowmo;        nowmo=lcm(nowmo,othmo);    }    printf("%d\n",ans);    return 0;}

5.容斥原理

小学数学,,,原理都懂,关键是看如何实现。这里是用一个类似队列来实现的。
给出的例题是求区间内与一个数互质的个数。

#include<iostream>#include<cmath>#include<cstdio>#include<cstdlib>using namespace std;const int maxn=200005;int q[maxn],jl[maxn];int a,b,n,tot=0;void divide(int n){    tot=0;    for(int i=2;i*i<=n;i++)    {        if(n%i==0)        {            jl[++tot]=i;            while(n%i==0)                n/=i;        }    }        if(n>1) //最后还剩下一个大质数        jl[++tot]=n;}int rc(int n)   //利用容斥原理理的思想:去求区间[1,r]中不与n互素的个数。 {    int sum=0,tmp=0;    q[0]=-1;    for(int i=1;i<=tot;i++)    {        int xx=tmp;        for(int j=0;j<=xx;j++)            q[++tmp]=q[j]*jl[i]*(-1);    }    for(int i=1;i<=tmp;i++)        sum+=n/q[i];    return sum;}int main(){    int n,a,b,cnt=0;    scanf("%d%d%d",&a,&b,&n);    divide(n);    printf("%d\n",(b-rc(b))-(a-1-rc(a-1)));    return 0;} 

6.逆元(a与m互质)

如果有,则把这个同余方程中的最小正整数解叫做模的逆元。逆元一般用扩展欧几里得算法来求得,如果为素数,那么还可以根据费马小定理得到逆元为 推导如下:
这里写图片描述

7.错位排序

就是所有的东西全被放错的可能出现的种数。证明过程(以下摘自百度百科):瑞士数学家欧拉按一般情况给出了一个递推公式:用A、B、C……表示写着n位友人名字的信封,a、b、c……表示n份相应的写好的信纸。把错装的总数为记作f(n)。假设把a错装进B里了,包含着这个错误的一切错装法分两类:
(1)b装入A里,这时每种错装的其余部分都与A、B、a、b无关,应有f(n-2)种错装法。
(2)b装入A、B之外的一个信封,这时的装信工作实际是把(除a之外的)(n-1 )份信纸b、c……装入(除B以外的)n-1个信封A、C……,显然这时装错的方法有f(n-1)种。
总之在a装入B的错误之下,共有错装法f(n-2)+f(n-1)种。a装入C,装入D……的n-2种错误之下,同样都有f(n-2)+f(n-1)种错装法,因此:f(n)=(n-1) {f(n-1)+f(n-2)}
公式可重新写成 f(n)-nf(n-1)=-[f(n-1)-(n-1)f(n-2)] (n>2)
于是可以得到
f(n)-nf(n-1)=-[f(n-1)-(n-1)f(n-2)]
=((-1)^2)[f(n-2)-(n-2)f(n-3)]
=((-1)^3)[f(n-3)-(n-3)f(n-4)]
=……
=[(-1)^(n-2)][f(2)-2f(1)]
最后可以整理为如下:f(n)=nf(n-1)+(-1)^(n-2)
f[1]=0,f[2]=1,f[3]=9,f[4]=44;

原创粉丝点击