[SinGuLaRiTy] 复习模板-数学

【SinGuLaRiTy-1047】 Copyright (c) SinGuLaRiTy 2017. All Rights Reserved.

质因数分解

void solve(int n)
{
    while(n%2==0)
    {
        printf("%d*",2);
        n/=2;
    }
    for(int i=3;i<=sqrt(n);i+=2)
    {
        if(n%i==0)
        {
            n/=i;
            printf("%d*",i);
            i-=2;
        }
    }
    printf("%d\n",n);
}

 

欧拉线性筛素数

#define MAXN 100005
#define MAXL 1299710

int prime[MAXN];
int check[MAXL];

int tot=0;

void get_prime()
{
    for(int i=2;i<MAXL;i++)
    {
          if(!check[i])
            prime[tot++]=i;
          for(int j=0;j<tot;j++)
          {
            if(i*prime[j]>MAXL)
                break;
            check[i*prime[j]]=1;
            if(i%prime[j]==0)
                 break;
          }
    }
}

 

筛法求欧拉函数(线性)

#include<iostream>  
#include<cstdio>  
#define N 40000
using namespace std;
int n; int phi[N+10],prime[N+10],tot,ans; bool mark[N+10];
void getphi() { int i,j; phi[1]=1; for(i=2;i<=N;i++)//相当于分解质因式的逆过程 { if(!mark[i]) { prime[++tot]=i;//筛素数的时候首先会判断i是否是素数。 phi[i]=i-1;//当 i 是素数时 phi[i]=i-1 } for(j=1;j<=tot;j++) { if(i*prime[j]>N)
break; mark[i*prime[j]]=1;//确定i*prime[j]不是素数 if(i%prime[j]==0)//接着我们会看prime[j]是否是i的约数 { phi[i*prime[j]]=phi[i]*prime[j];
break; } else
phi[i*prime[j]]=phi[i]*(prime[j]-1);//其实这里prime[j]-1就是phi[prime[j]],利用了欧拉函数的积性 } } }

 

Miller-Rabbin素数判定法

#include<cstdlib>
#include<ctime>
#include<cstdio>

using namespace std;

const int count=10;

int modular_exp(int a,int m,int n)
{
    if(m==0)
        return 1;
    if(m==1)
        return (a%n);
    long long w=modular_exp(a,m/2,n);
    w=w*w%n;
    if(m&1)
        w=w*a%n;
    return w;
} 

bool Miller_Rabin(int n)
{
    if(n==2)
        return true;
    for(int i=0;i<count;i++)
    {
        int a=rand()%(n-2)+2;
        if(modular_exp(a,n,n)!=a)
            return false;
    }
    return true;
}

int main()
{
    srand(time(NULL));
    int n;
    scanf("%d",&n);
    if(Miller_Rabin(n))
        printf("Probably a prime.");
    else
        printf("A composite.");
    printf("\n");
    return 0;
}

 

倍增求快速幂

typedef long long ll;

ll fast_power(ll a,ll b,ll c)//求a^b%c
{
    ll ans=1;
    while(b)
    {
        if (b&1)
            ans=ans*a%c; 
        else 
            ans%=c;
        b>>=1;
        a=a*a%c; 
    }
    return ans;
}

 

大数乘法取幂

typedef long long ll

ll qmul(ll x,ll y,ll mod)// 乘法防止溢出,如果p*p不爆LL的话可以直接乘;O(1)乘法或者转化成二进制加法(快速加)
{
    ll ret=0;
    while(y) 
    {
        if(y&1)
            ret=(ret+x)%mod;
        x=x*2%mod;
        y>>=1;
    }
    return ret;
}

ll qpow(ll a,ll n,ll mod)
{
    ll ret=1;
    while(n)
    {
        if(n&1) 
            ret=qmul(ret,a,mod);
        a=qmul(a,a,mod);
        n>>=1;
    }
    return ret;
}

 

GCD & LCM

LL gcd(LL a,LL b)
{
    return b ? gcd(b,a%b) : a;
}

 

至于LCM=a*b/GCD,可能会因为a*b过大而爆掉,于是推荐使用LCM=a/GCD*b

Exgcd

求ax=b (mod m) ax+my=b 如果r=gcd(a,m)且b%r==0,则同余方程有解,其最小解为x*(b/r);
ax+by=c 如r=gcd(a,b),则存在x,y,使xa+yb=r;当x+=b,y-=a后仍然成立        
因为xa+yb+ab-ab=r;则(x+b)a+(y-a)b=r

int exgcd(int a,int b,int &x,int &y)
{
     if(b==0)
    {
        x=1;
        y=0;
        return a;
    }
     int r=exgcd(b,a%b,y,x);
     y-=x*(a/b);
     return r;
}

中国剩余定理

设正整数m1,m2,m3...mk两两互素,则同余方程组

有整数解。并且在模M=m1·m2·...·mk下的解是唯一的,解为

 

其中,而的逆元。

void extend_Euclid(int a,int b,int &x,int &y)  
{  
    if(b==0)  
    {  
        x=1;  
        y=0;  
        return;  
    }  
    extend_Euclid(b,a%b,x,y);  
    int tmp=x;  
    x=y;  
    y=tmp-(a/b)*y;  
}  
  
int CRT(int a[],int m[],int n)  
{  
    int M=1;  
    int ans=0;  
    for(int i=1;i<=n;i++)  
        M*=m[i];  
    for(int i=1;i<=n;i++)  
    {  
        int x,y;  
        int Mi=M/m[i];  
        extend_Euclid(Mi,m[i],x,y);  
        ans=(ans+Mi*x*a[i])%M;  
    }  
    if(ans<0) 
        ans+=M;  
    return ans;  
}  

Catalan数

f[1]=1;
for(int i=2;i<=n;i++)
    f[i]=f[i-1]*(4*i-2)/(i+1);

Catalan数有许多神奇的性质,这是一篇总结的比较精炼的文章。《Catalan数——卡特兰数》

康托展开式

int  fac[] = {1,1,2,6,24,120,720,5040,40320}; //i的阶乘为fac[i]  
// 康托展开-> 表示数字a是 a的全排列中从小到大排,排第几  
// n表示1~n个数  a数组表示数字。  
int kangtuo(int n,char a[])  
{  
    int i,j,t,sum;  
    sum=0;  
    for( i=0;i<n;++i)  
    {  
        t=0;  
        for(j=i+1;j<n;++j)  
            if(a[i]>a[j])  
                ++t;  
        sum+=t*fac[n-i-1];  
    }  
    return sum+1;  
}  

有啥用?它可以应用于哈希表中空间压缩。在码某些搜索题时,将VIS数组量压缩。比如:八数码魔板

求乘法逆元

exgcd,老熟人了。

/* 
求解ax+by=gcd(a,b),亦即ax≡1(mod b)。函数返回值是a,b的最大公约数,而x即a的逆元。 
注意a, b不能写反了。 
*/  
int x,y;  
int extgcd(int a,int b,int &x,int &y)  
{  
    if(b==0)
    {  
        x=1;  
        y=0;  
        return a;  
    }  
    int gcd=exgcd(b,a%b, x, y);  
    int tmp=x;  
    x=y;  
    y=tmp-(a/b)*y;  
    return gcd;  
}  

N的全排列

#include<iostream>

using namespace std;

void swap(int &a,int &b)
{
    int temp=a;
    a=b;
    b=temp;
}

void pai_xu(int a[],int m,int n)
{
    if(m==n)
        {
        for(int i=1;i<=n;i++)
            cout<<a[i];
        cout<<endl; 
    }
    else
        for(int i=m;i<=n;i++)
                {
            swap(a[i],a[m]);
            pai_xu(a,m+1,n);
            swap(a[i],a[m]);
        }
}

int main()
{
    int n,m=1,a[10];
    cin>>n;
    for(int i=1;i<=n;i++)
        a[i]=i;
    pai_xu(a,m,n);
    return 0;
}

N的R排列

#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<cmath>

using namespace std;

bool b[21]={0};
int total=0,a[21]={0};

bool pd(int x,int y)
{
     int k=2,i=x+y;
     while(k<=sqrt(i)&&i%k!=0)
         k++;
     if(k>sqrt(i))
         return 1;
     else 
         return 0;
}

int print()
{
    total++;
    cout<<total<<" "<<endl;
    for(int i=1;i<=20;i++)
        cout<<a[i]<<" ";
    cout<<endl;
}

int search(int t)
{
    for(int i=1;i<=20;i++)
        if(pd(a[t-1],i)&&(!b[i]))
        {
            a[t]=i;
            b[i]=1;
            if(t==20)
            {
                if(pd(a[20],a[1]))
                    print();
            }
            else 
                search(t+1);
            b[i]=0;
        }
}

int main()
{
    search(1);
    cout<<total<<endl;
    return 0;
}

有重复元素的全排列

void fun(int n,char chars[],int flag) {  
    if(flag==n-1) {  
        for(int i=0;i<n;i++) cout<<chars[i];  
        cout<<endl;  
        return;  
    }  
    for(int i=flag;i<n;i++) {  
        if(chars[i]!=chars[flag]||i==flag){//若两个元素不相等或者两个元素的下标相同的时候才调用  
            swap(chars[i],chars[flag]);  
            fun(n,chars,flag+1);  
            swap(chars[i],chars[flag]);  
        }  
    }  
}  

第一类Stirling数

定理:第一类Stirling数s(p,k)计数的是把p个对象排成k个非空循环排列的方法数。

证明:把上述定理叙述中的循环排列叫做圆圈。递推公式为:

       s(p,p)=1 (p>=0)    有p个人和p个圆圈,每个圆圈就只有一个人

       s(p,0)=0 (p>=1)    如果至少有1个人,那么任何的安排都至少包含一个圆圈

       s(p,k)=(p-1)*s(p-1,k)+s(p-1,k-1)

       设人被标上1,2,.....p。将这p个人排成k个圆圈有两种情况。第一种排法是在一个圆圈里只有标号为p的人自己,排法有s(p-1,k-1)个。第二种排法中,p至少和另一个人在一

个圆圈里。这些排法可以通过把1,2....p-1排成k个圆圈再把p放在1,2....p-1任何一人的左边得到,因此第二种类型的排法共有(p-1)*s(p-1,k)种排法。

       在证明中我们所做的就是把{1,2,...,p}划分到k个非空且不可区分的盒子,然后将每个盒子中的元素排成一个循环排列。

long long s[maxn][maxn];//存放要求的第一类Stirling数  
const long long mod=1e9+7;//取模  
  
void init()//预处理  
{  
    memset(s,0,sizeof(s));  
    s[1][1]=1;  
    for(int i=2;i<=maxn-1;i++)  
        for(int j=1;j<=i;j++)  
    {  
        s[i][j]=s[i-1][j-1]+(i-1)*s[i-1][j];  
        if(s[i][j]>=mod)  
            s[i][j]%=mod;  
    }  
}  

第二类Stirling数

定理:第二类Stirling数S(p,k)计数的是把p元素集合划分到k个不可区分的盒子里且没有空盒子的划分个数。

证明:元素在拿些盒子并不重要,唯一重要的是各个盒子里装的是什么,而不管哪个盒子装了什么。

        递推公式有:S(p,p)=1 (p>=0)         S(p,0)=0  (p>=1)         S(p,k)=k*S(p-1,k)+S(p-1,k-1)   (1<=k<=p-1)。考虑将前p个正整数,1,2,.....p的集合作为要被划分的集合,把

{1,2,.....p}分到k个非空且不可区分的盒子的划分有两种情况:

       (1)那些使得p自己单独在一个盒子的划分,存在有S(p-1,k-1)种划分个数

       (2)那些使得p不单独自己在一个盒子的划分,存在有 k*S(p-1,k)种划分个数

        考虑第二种情况,p不单独自己在一个盒子,也就是p和其他元素在一个集合里面,也就是说在没有放p之前,有p-1个元素已经分到了k个非空且不可区分的盒子里面(划

分个数为S(p-1,k),那么现在问题是把p放在哪个盒子里面呢,有k种选择,所以存在有k*S(p-1,k)。

long long s[maxn][maxn];//存放要求的Stirling数  
const long long mod=1e9+7;//取模  
  
void init()//预处理  
{  
    memset(s,0,sizeof(s));  
    s[1][1]=1;  
    for(int i=2;i<=maxn-1;i++)  
        for(int j=1;j<=i;j++)  
    {  
        s[i][j]=s[i-1][j-1]+j*s[i-1][j];  
        if(s[i][j]>=mod)  
            s[i][j]%=mod;  
    }  
}  

注意:要用long long类型,当元素个数>20,就超int类型了。

扩展:k! *S(p,k) 计数的是把p元素集合划分到k个可区分的盒子里且没有空盒子的划分个数。

组合数

//组合数打表模板,适用于N<=3000
//c[i][j]表示从i个中选j个的选法。
long long C[N][N];
void get_C(int maxn)
{
    C[0][0] = 1;
    for(int i=1;i<=maxn;i++)
    {
        C[i][0] = 1;
        for(int j=1;j<=i;j++)
            C[i][j] = C[i-1][j]+C[i-1][j-1];
        //C[i][j] = (C[i-1][j]+C[i-1][j-1])%MOD;
    }
}

LUCAS定理

long long F[100010];  
void init(long long p)  
{  
    F[0] = 1;  
    for(int i = 1;i <= p;i++)  
        F[i] = F[i-1]*i % (1000000007);  
}  
long long inv(long long a,long long m)  
{  
    if(a == 1)return 1;  
    return inv(m%a,m)*(m-m/a)%m;  
}  
long long Lucas(long long n,long long m,long long p)  
{  
    long long ans = 1;  
    while(n&&m)  
    {  
        long long a = n%p;  
        long long b = m%p;  
        if(a < b)return 0;  
        ans = ans*F[a]%p*inv(F[b]*F[a-b]%p,p)%p;  
        n /= p;  
        m /= p;  
    }  
    return ans;  
}  

容斥原理

这个怎么打板呢?难道搞一道容斥的题?还是偷偷懒附个链接吧:

《算法中的容斥原理》

 

Time: 2017-11-05

posted @ 2017-11-05 16:43  SinGuLaRiTy2001  阅读(276)  评论(0编辑  收藏  举报