数论总结(详细)


 

线性筛:

所有积性函数都能使用线性筛,线性筛保证每个数只会被它的最小质因子给筛掉,所以时间是线性的。

线性筛质数:

void init(int n)
{
    for(ri i=2;i<=n;++i){
        if(!pri[i]) su[++cnt]=i;
        for(ri j=1;j<=cnt && su[j]*i<=n;++j){
            int k=su[j]*i;
            pri[k]=1;
            if(i%su[j]==0) break;
        }
    }
}
View Code

线性筛约数个数与约数和公式详解

线性筛约数个数:

void init(int n)
{
    for(ri i=2;i<=n;++i){
        if(!pri[i]) su[++cnt]=i,num[i]=2,tot[i]=1;//当i为质数的时候 正约数的个数为 1与i 
        for(ri j=1;j<=cnt && su[j]*i<=n;++j){
            int k=su[j]*i;
            pri[k]=1;
            if(i%su[j]==0){
                num[k]=num[i]/(tot[i]+1)*(tot[i]+2);//质因子个数从原来的tot+1 变成tot+2 
                tot[k]=tot[i]+1;//在i的基础上多了su[j]这个质数 
                break;
            }//积性函数的性质 
            else num[k]=num[i]*num[su[j]],tot[k]=1;//i*su[j]包含了su[j]这个质因子 tot从1开始重新统计贡献 
        }
    }
}
View Code

线性筛约数和:

void init(int n)
{
    for(ri i=2;i<=n;++i){
        if(!pri[i]) su[++cnt]=i,sum1[i]=1+i,sum2[i]=1+i;//质数的约数只有1和它本身 
        for(ri j=1;j<=cnt&&su[j]*i<=n;++j){
            int k=su[j]*i;
            pri[k]=1;
            if(i%su[j]==0){
                sum1[k]=sum1[i]/sum2[i]*(sum2[i]*su[j]+1);//去掉原有的 乘上现在的 
                sum2[k]=sum2[i]*su[j]+1;
                break;
            }
            else{
                sum1[k]=sum1[i]*(su[j]+1);//su[j]+1==sum1[su[j]] 
                sum2[k]=1+su[j];//从下一个新的约数开始统计贡献 
            } 
        }
    }
}
View Code

线性筛莫比乌斯函数:

void init()
{
    u[1]=1;
    for(int i=2;i<=maxn;i++){
        if(!pri[i]) su[++cnt]=i,u[i]=-1;
        for(int j=1;j<=cnt&&su[j]*i<=maxn;j++){
            if(i%su[j]==0){
                pri[i*su[j]]=1; u[i*su[j]]=0;
                break;
            }
            pri[i*su[j]]=1;
            u[i*su[j]]=-u[i];
        }
    }
}
View Code

线性筛欧拉函数:

void init(int n)
{
    for(ri i=2;i<=n;++i){
        if(!pri[i]) su[++cnt]=i,ol[i]=i-1;
        for(ri j=1;j<=cnt&&su[j]*i<=n;++j){
            pri[su[j]*i]=1;
            if(i%su[j]==0){
                ol[su[j]*i]=ol[i]*su[j];
                break;
            }
            else ol[su[j]*i]=ol[i]*(su[j]-1);
        }
    }
}
View Code

线性筛好题


 

质数 && 约数:

1.求 l~r 内素数的个数:洛谷P1835 素数密度_NOI导刊2011提高(04)

数论里面,对于 l 和 r 很大,r-l 范围又很小的时候,都用到了用一个数组x[ i ]表示 i+l 的值,也就是把 l~r 这个区间对应到 0~l-1 中了。

同时也预处理了可能对答案做出贡献的值(一般是预处理根号内的素数)。

#include<bits/stdc++.h>
using namespace std;
#define N 1000005
#define ll long long
#define ri register int
int cnt=0,ans=0,pri[N],su[N],fl[N];
ll l,r;
void suu(int n)
{
    for(ri i=2;i<=n;++i){
        if(!pri[i]) su[++cnt]=i;
        for(ri j=1;j<=cnt&&su[j]*i<=n;++j){
            pri[su[j]*i]=1;
            if(i%su[j]==0) break;
        }
    }
}
int main()
{
    scanf("%lld%lld",&l,&r);
    suu(sqrt(r));
    for(int i=1;i<=cnt;++i){
        for(ll j=l/su[i]*su[i];j<=r;j+=su[i]){
            if(j>=l && j!=su[i]) fl[j-l]=1;
        }
    }
    for(ll i=l;i<=r;++i) if(fl[i-l]==0) ans++;
    printf("%d
",ans);
}
/*
11
*/

筛大素数
View Code

2.求A^B所有约数和(A,B<=5e7):洛谷P1593 因子和

题解

3.求1~n范围里约数的约数的个数加起来最多的是哪个数 及其总数

题解4.给定n和m,求Σ(1<=i<=n)   Σ(1<=j<=m)   GCD(i,j)*2-1  :洛谷P1447 [NOI2010]能量采集

/*
来源:洛谷第一篇题解 
题目大意:给定n和m,求Σ(1<=i<=n)Σ(1<=j<=m)GCD(i,j)*2-1

i和j的限制不同,传统的线性筛法失效了,这里我们考虑容斥原理

令f[x]为GCD(i,j)=x的数对(i,j)的个数,这个不是很好求

我们令g[x]为存在公因数=x的数对(i,j)的个数(注意不是最大公因数!),显然有g[x]=(n/x)*(m/x)

但是这些数对中有一些的最大公因数为2d,3d,4d,我们要把他们减掉

于是最终f[x]=(n/x)*(m/x)-Σ(2*x<=i*x<=min(m,n))f[i*x]

从后向前枚举x即可

时间复杂度O(nlogn)
*/
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define N 200005
ll n,m,f[N],ans=0;
int main()
{
    scanf("%lld%lld",&n,&m);
    ll minn=min(n,m);
    for(ll i=minn;i>=1;--i){
        //计算n,m中有多少对公约数为 i的(注意不是最大公约数) 
        f[i]=(n/i)*(m/i);
        //减去公约数为i*2 i*3 ...的 
        for(ll j=i*2;j<=minn;j+=i) f[i]-=f[j];
        //根据题意统计答案 
        ans+=f[i]*(2*i-1);
    }
    printf("%lld
",ans);
}
/*
5 4
*/
代码+题解

BSGS:

BSGS模板(互质与不互质) 洛谷P4195、P3846

没有做到不是模板的BSGS


中国剩余定理:

1.模板:洛谷P3868 [TJOI2009]猜数字

注意有坑

2.扩展+模板+详解

3.综合运用:P2480 [SDOI2010]古代猪文

#include<bits/stdc++.h>
using namespace std;
#define Mod 999911659
#define ll long long
#define ri register int 
#define N 40005
ll p[5]={0,2,3,4679,35617},fac[N],xx[5];
ll quick_pow(ll a,ll k,ll mod)
{
    ll ans=1;
    while(k){ if(k&1) ans=ans*a%mod; a=a*a%mod; k>>=1; }
    return ans;
}
void init(ll n)
{
    fac[0]=1; for(ll i=1;i<=n;++i) fac[i]=fac[i-1]*i %n;
}
ll C(ll n,ll m,ll mod)
{
    if(n<m) return 0;
    return fac[n]* quick_pow(fac[n-m],mod-2,mod) %mod *quick_pow(fac[m],mod-2,mod) %mod;
}
ll lucas(ll n,ll m,ll mod)
{
    if(n<m) return 0;
    if(m==0 || n==0) return 1; 
    return lucas(n/mod,m/mod,mod) * C(n%mod,m%mod,mod) %mod;
}
void work1(ll n)
{
    for(ri i=1;i<=4;++i){
        init(p[i]);
        for(ri j=1;j*j<=n;++j)
        if(n%j==0){
            xx[i]=( xx[i] + lucas(n,j,p[i]) ) %p[i];
            if(j*j!=n) xx[i]=( xx[i] + lucas(n,n/j,p[i]) ) %p[i];
        }
    }
}
ll exgcd(ll a,ll b,ll &x,ll &y)
{
    if(!b) { x=1; y=0; return a; }
    ll d=exgcd(b,a%b,x,y);
    ll z=x; x=y; y=z-(a/b)*y;
    return d;
}
/*ll work2()中国剩余定理用扩欧求会TLE。。。 
{
    ll M=Mod-1,ans=0;
    for(ri i=1;i<=4;++i){
        ll mi=M/p[i],a=mi,b=p[i];
        ll x,y,d=exgcd(a,b,x,y);
        ans=( ans + x*mi %M *xx[i] %M ) %M;
    }
    return ans;
}*/
ll work2()
{
    ll ans=0,M=Mod-1;//直接用逆元解同余方程 
    for(ri i=1;i<=4;++i) ans=( ans+ quick_pow(M/p[i],p[i]-2,p[i]) %M *xx[i] %M *(M/p[i]) %M) %M;
    return ans;
}
int main()
{
    ll q,n;
    scanf("%lld%lld",&n,&q);
    if(q==Mod) {
        printf("0
"); return 0;
    }
    work1(n);
    printf("%lld
",quick_pow(q,work2(),Mod));
}
/*
4 2

237573745 375736583
*/
View Code

高斯消元:

详解


矩阵乘法:

详解


欧拉函数 && 欧拉定理:

详解


exgcd:

1.P1516 青蛙的约会 :列式子求exgcd即可(注意判负数)

#include<bits/stdc++.h>
using namespace std;
#define ll long long
ll exgcd(ll a,ll b,ll &x,ll &y)
{
    if(!b) { x=1; y=0; return a; }
    ll d=exgcd(b,a%b,x,y);
    ll z=x; x=y; y=z-(a/b)*y;
    return d;
}
int main()
{
    ll xx,yy,m,n,L;
    scanf("%lld%lld%lld%lld%lld",&xx,&yy,&m,&n,&L);
    ll a=m-n,b=L,c=yy-xx;
    //if(a<0) a=-a,c=-c;
    ll x,y,d=exgcd(a,b,x,y);
    //printf("%lld %lld %lld %lld =%lld
",a,x,b,y,d);
    if(c%d) printf("Impossible
");
    else{
        x=( (c/d)*x %(abs(b/d)) +abs(b/d) ) %abs(b/d);//mod一定是正数!! 否则mod出来也会是负数 
        printf("%lld
",x);
    }
    return 0;
}
View Code

2.[NOI2002]荒岛野人枚举模数,用exgcd检查即可。

#include<bits/stdc++.h>
using namespace std;
#define N 100005
int n,c[N],p[N],l[N];
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,y,x);
    y=y-(a/b)*x;
    return d;
}
bool check(int m)
{
    for(int i=1;i<=n;i++)
     for(int j=i+1;j<=n;j++)
     {
         int a=p[i]-p[j],b=m,cc=c[j]-c[i],x,y;
         int d=exgcd(a,b,x,y);
        if(cc%d) continue;//无解成立
        //x是相遇的最小整数天数 如果大于寿命则成立 
        //c%d==0 所以对应的x应该为x*(c/d);
        b=abs(b/d);
        x=(x*(cc/d)%b+b)%b;
        if(x<=l[i]&&x<=l[j]) return 0;//会相遇就直接判m不成立 
     }
     return 1; 
}
int main()
{
    int mx=0;
    scanf("%d",&n);
    for(int i=1;i<=n;i++) 
     scanf("%d%d%d",&c[i],&p[i],&l[i]),mx=max(c[i],mx);
    for(int i=mx;;i++)
    if(check(i)) return printf("%d
",i),0;
}
/*
3
1 3 4
2 7 3
3 2 1
ans 6
*/
View Code

3.求解方程的正整数解个数,及和:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
ll gcd(ll a,ll b) { return b ? gcd(b,a%b) : a; }
ll exgcd(ll a,ll b,ll &x,ll &y)
{
    if(!b) { x=1; y=0; return a; }
    ll d=exgcd(b,a%b,x,y);
    ll z=x; x=y; y=z-(a/b)*y;
    return d;
}
int main()
{
    freopen("pay.in","r",stdin);
    freopen("pay.out","w",stdout);
    int T,opt;
    scanf("%d%d",&T,&opt);
    while(T--){
        ll a,b,c;
        scanf("%lld%lld%lld",&a,&b,&c);
        ll x,y,d=exgcd(a,b,x,y);
        if(c%d){
            if(opt==1) printf("0
");
            else printf("0 0
");
            continue;
        }
        x=( (c/d)*x %(b/d) + b/d ) % (b/d);//把x变成最小非负整数解  b/d是x的增量  
        y=( c-a*x )/b;
        if(y<0){//如果说x的最小非负整数解y都小于0了 说明只有一个为负 一个为正的解 不符合题意 
            if(opt==1) printf("0
");
            else printf("0 0
");
            continue;
        }
        ll ans1=0,ans2=0;
        /*while(y>=0){//暴力跳解 其实只需要求一下等差序列的和即可 
            if(x>=0 && y>=0) ans1++,ans2+=x+y;
            x+=b/d; y-=a/d;
        }*/
        int x1=y/(a/d),x2=y%(a/d);//推公式求等差序列 
        ans2+=(a/d)*(x1+1)*x1/2 + (x1+1)*x2;
        ans2+=(b/d)*(x1+1)*x1/2 + (x1+1)*x;
        ans1=x1+1;//推式子 
        if(opt==1) printf("%lld
",ans1);
        else printf("%lld %lld
",ans1,ans2);
    }
}
/*
2 2

6 9 3

3 4 21
2 4 12
*/
View Code

注意x的增量是:b/d,通过求特解来求出所有满足情况的解。


逆元:

求逆元的几种常用方法


排列组合:

1. 组合数的常用公式大全

2.P4071 [SDOI2016]排列计数 :错排递推+组合数

//luogu4071 
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ri register int
#define N 1000005
const ll mod=1e9+7;
ll f[N],invfac[N],fac[N];
ll C(int n,int m)
{
    return fac[n]*invfac[n-m] %mod *invfac[m] %mod;
}
ll quick_pow(ll a,ll k)
{
    ll ans=1;
    while(k){
        if(k&1) ans=ans*a%mod; a=a*a%mod; k>>=1;
    }
    return ans;
}
void init()
{
    int nn=1e6;
    f[0]=1; f[1]=0; f[2]=1;
    for(ri i=3;i<=nn;++i) f[i]=(i-1)*( ( f[i-1]+f[i-2]) %mod ) %mod;
    fac[0]=1;
    for(ri i=1;i<=nn;++i) fac[i]=fac[i-1]*i %mod;
    invfac[nn]=quick_pow(fac[nn],mod-2);
    for(ri i=nn;i>=1;--i) invfac[i-1]=invfac[i]*i %mod;
}
int main()
{
    int T,n,m;
    scanf("%d",&T);
    init();
    while(T--){
        scanf("%d%d",&n,&m);
        if(n<m) printf("0
");
        else printf("%lld
",f[n-m]*C(n,m)%mod);
    }
}
View Code

3.problem 题(Catlan + dp + 组合数)

4.组合数插板法学习:(转载自here

/*排列组合插板法小结

插板法就是在n个元素间的(n-1)个空中插入若干个(b)个板,可以把n个元素分成(b+1)组的方法。

应用插板法必须满足三个条件:

(1)这n个元素必须互不相异

(2)所分成的每一组至少分得一个元素

 (3) 分成的组别彼此相异

举个很普通的例子来说明

把10个相同的小球放入3个不同的箱子,每个箱子至少一个,问有几种情况?

问题的题干满足条件(1)(2),适用插板法,c9 2=36

下面通过几道题目介绍下插板法的应用

===================================================

a 凑元素插板法(有些题目满足条件(1),不满足条件(2),此时可适用此方法)

例1 :把10个相同的小球放入3个不同的箱子,问有几种情况?

3个箱子都可能取到空球,条件(2)不满足,此时如果在3个箱子种各预先放入

1个小球,则问题就等价于把13个相同小球放入3个不同箱子,每个箱子至少一个,有几种情况?

显然就是c12 2=66

-------------------------------------------------

例2:把10个相同小球放入3个不同箱子,第一个箱子至少1个,第二个箱子至少3个,第三个箱子可以放空球,有几种情况?

我们可以在第二个箱子先放入10个小球中的2个,小球剩8个放3个箱子,然后在第三个箱子放入8个小球之外的1个小球,则问题转化为把9个相同小球放3不同箱子,每箱至少1个,几种方法?c8 2=28

==================================================

b 添板插板法

例3:把10个相同小球放入3个不同的箱子,问有几种情况?

-o - o - o - o - o - o - o - o - o - o - o表示10个小球,-表示空位

11个空位中取2个加入2块板,第一组和第三组可以取到空的情况,第2组始终不能取空

此时若在第11个空位后加入第12块板,设取到该板时,第二组取球为空

则每一组都可能取球为空c12 2=66

--------------------------------------------------------

例4:有一类自然数,从第三个数字开始,每个数字都恰好是它前面两个数字之和,直至不能再写为止,如257,1459等等,这类数共有几个?

因为前2位数字唯一对应了符合要求的一个数,只要求出前2位有几种情况即可,设前两位为ab

显然a+b<=9 ,且a不为0

1 -1- 1 -1 -1 -1 -1 -1 -1 - - 1代表9个1,-代表10个空位
插一个板使得b可以为0 
我们可以在这9个空位中插入2个板,分成3组,第一组取到a个1,第二组取到b个1,但此时第二组始终不能取空,若多添加第10个空时,设取到该板时第二组取空,即b=0,所以一共有c10 2=45

-----------------------------------------------------------

例5:有一类自然数,从第四个数字开始,每个数字都恰好是它前面三个数字之和,直至不能再写为止,如2349,1427等等,这类数共有几个?

类似的,某数的前三位为abc,a+b+c<=9,a,b不为0

1 -1- 1 -1 -1 -1 -1 -1 -1 - - -
这道题只需要保证a不为0 但是插3个板会使b,c不为0 而这道题b,c可以为0 所以插两个板表示它们取空的情况  
在9个空位中插入3板,分成4组,第一组取a个1,第二组取b个1,第三组取c个1,由于第二,第三组都不能取到空,所以添加2块板

设取到第10个板时,第二组取空,即b=0;取到第11个板时,第三组取空,即c=0。所以一共有c11 3=165

============================================

c 选板法

例6:有10粒糖,如果每天至少吃一粒(多不限),吃完为止,求有多少种不同吃法?

o - o - o - o - o - o - o - o - o - o o代表10个糖,-代表9块板

10块糖,9个空,插入9块板,每个板都可以选择放或是不放,相邻两个板间的糖一天吃掉

这样一共就是2^9= 512啦

=============================================

d 分类插板

例7:小梅有15块糖,如果每天至少吃3块,吃完为止,那么共有多少种不同的吃法?

此问题不能用插板法的原因在于没有规定一定要吃几天,因此我们需要对吃的天数进行分类讨论

最多吃5天,最少吃1天

1:吃1天或是5天,各一种吃法一共2种情况

2:吃2天,每天预先吃2块,即问11块糖,每天至少吃1块,吃2天,几种情况?c10 1=10

3:吃3天,每天预先吃2块,即问9块糖,每天至少1块,吃3天? c8 2=28

4:吃4天,每天预先吃2块,即问7块糖,每天至少1块,吃4天?c6 3=20

所以一共是2+10+28+20=60 种

=================================

e 二次插板法

例8 :在一张节目单中原有6个节目,若保持这些节目相对次序不变,再添加3个节目,共有几种情况?

-o - o - o - o - o - o - 三个节目abc
三个一起不好处理 可以分成一个一个地处理 
可以用一个节目去插7个空位,再用第二个节目去插8个空位,用最后个节目去插9个空位

所以一共是c7 1×c8 1×c9 1=504种

-----------------------------------------------------------*/
View Code

待更新。。。

原文地址:https://www.cnblogs.com/mowanying/p/11687915.html