程序竞赛中的数论

取模运算

         编程竞赛有相当一部分题目的数据结果过于庞大,往往需要对结果取模。例如(a*b) % p,若a*b的结果存储不了,再去取模,结果显然不对,为了防止溢出,可以分别对a取模,b取模,再求积取模。

   取模运算公式:
         加法:(a + b) % p = (a%p + b%p) % p
         减法:(a - b) % p = ((a%p - b%p) + p) % p
         乘法:(a * b) % p = (a%p)*(b%p) % p
         幂运算:a ^ b % p = ((a % p)^b) % p
         除法:(a / b) % p  = (a%p * inv%p) % p

    注意:(a / b) % p不能直接 / b 来求,需要找到一个数 inv 使得  inv * b % p= 1 。 我们称inv是逆元,下面介绍求逆元的方法。

         由于在竞赛中,通常让我们把结果 % 1e9+7 ,所以推荐使用方法一。当然如果a刚好是b的倍数可以直接计算。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod = 1e9+7;

// 方法一/方法二适用范围:mod为素数
// 方法一:递归求逆元
ll getInv(ll b) {
    if(b==1) return 1;
    return (mod-mod/b) * getInv(mod%b)%mod;
}

// 方法二:快速幂求逆元:根据费马小定理,(a/b)%mod = a*pow_mod(b,mod-2)%mod
// 快速幂原理举例:11二进制1011 ==> 11 = 2^0+2^1+2^3
//               2^11 = 2^(2^0) * 2^(2^1) * 2^(2^3)
ll pow_mod(ll x,ll n) {
    ll res=1;
    while(n > 0) {
        if(n & 1)    res = res*x % mod;
        x = x*x % mod;
        n >>= 1;
    }
    return res;
}

// 方法三:线性求逆元,可得到每个数对应的逆元
const int N = 100005;
long long inv[N];
void inv_init() {
    inv[0] = inv[1] = 1;
    for (int i = 2; i < N; i++) {
        inv[i] = ((mod - mod/ i) * inv[mod % i]) % mod;
    }
}

int main(){
    ll a, b;
    cin >> a >> b;
    ll inv = getInv(b);
    // ll inv = pow_mod(b, mod-2);
    ll res = a%mod * inv%mod;
    cout << res << endl;
    return 0;
}

大数取模的二进制方法

/*---------------------------------------------------
  求 a^b mod c 
  把b化成二进制串的形式:b = (at at-1 at-2 … a1 a0) 
  那么有: b = at*2^t + at-1*2^(t-1) + … … + a1*2^1 + a0*2^0, 其中ai=0,1. 
  则:a^b mod c = a^( at*2^t + at-1*2^(t-1) + … … + a1*2^1 + a0*2^0) mod c = 
    ((a^(a0*2^0) mod c) * a^(a1*2^1) mod c)… … 
  注意到:a^(2^(i+1))mod c = (a^(2^i) mod c)^2 mod c,
  这样就可以在常数项时间内由2^i项推出2^(i+1)项。 时间复杂度为O((logb)^3). 
----------------------------------------------------*/
int mod_exp(int a,int b0,int n)  //return a^b0 % n 
{     
    if( a > n ) a %= n;     
    int  i, d = 1, b[35];     
    for( i=0; i < 35; ++i ) {
        b[i] = b0 % 2;
        b0 /= 2;         
        if( b0 == 0 )  break;     
    } //b[i]b[i-1]...b[0]为b0的二进制表示     
    for( ;i >= 0; --i ){
        d = (d*d) % n; 
        if( b[i] == 1 )  d = (d*a)%n;     
    }     
    return d; 
}

快速幂

// n超过long long
typedef unsigned long long LL;
string str;
int input[10000005];
int output[10000005];
int len;
int sum=1,d=0,k=0;
void to_bin(string str)               //将大整数转换为二进制,转换后为逆序
{
    len=str.size();
    for(int i=0;i<len;i++)
        input[i]=str[i]-'0';
    memset(output,0,sizeof(output));
    sum=1,d=0,k=0;
    while(sum)
    {
        sum = 0;
        for(int i=0;i<len;i++){
            d = input[i] / 2;
            sum += d;
            if(i == (len - 1)){
                output[k++] = input[i] % 2;
            }
            else
                input[i+1] += (input[i]%2)*10;
            input[i] = d;
        }
    }
}
LL pow(LL a, int n[], LL p)    //快速幂 a^n % p
{
    LL ans = 1;
    int i=0;
    while(i<k)
    {
        if(n[i] == 1) ans = ans * a % p;
        a = a * a % p;
        i++;
    }
    return ans;
}
int main()
{
    LL a,b,c;
    while(cin>>a>>str>>c)
    {
        to_bin(str);
        cout<<pow(a,output,c)<<endl;
    }
}

筛选法求素数

const int N= 1e6; // 素数表范围
bool isp[N+5];

int len = 0, p[N+5];
void prime() { //推荐这个,较快
    isp[0] = isp[1] = true;
    for (int i = 2; i < maxn; i++) {
        if(!isp[i]) p[++len] = i;
        for (int j = 1; j <= len && p[j]*i < maxn; j++) {
            isp[i*p[j]] = true;
            if (i%p[j] == 0) break;
        }
    }
}

void prime() {
    for(int i=2; i <= N; i++)
        isp[i] = true;
    int n = sqrt(N);
    for(int i=2; i <= n; i++) if(isp[i]) {
        for(int j=2; i*j <= N; j++)
            isp[i*j] = false;
    }
}

求  n!的位数

#include <bits/stdc++.h>
using namespace std;
const double PI = acos(-1.0);
const double e  = exp(1.0);

int main() {
    int n ;
    cin >> n;

    //斯特林定理求N!的位数
    double res = log10(sqrt(2*PI*n)) + log10(n/e)*n + 1;
    cout << res << endl;

    // //log求N!的位数
    double ans = 1;
    for(int i=1;i <= n; i++)
        ans += log10((double)i);
    cout << ans << endl;
    return 0;
}

阶乘最后非零位

/*==================================================*\ 
 | 阶乘最后非零位,复杂度O(nlogn) 
\*==================================================*/ 
//返回该位, n以字符串方式传入 
#include <string.h> 
#define MAXN 10000 
const int mod[20]={1,1,2,6,4,2,2,4,2,8,4, 4,8,4,6,8,8,6,8,2}; 
int lastdigit(char* buf) {  
    int len=strlen(buf), a[MAXN], i, c, ret=1;  
    if (len==1)  return mod[buf[0]-'0'];  
    for (i=0;i<len;i++)  a[i]=buf[len-1-i]-'0';  
    for (; len; len-=!a[len-1]){   
        ret = ret * mod[a[1] % 2*10+a[0]] % 5;   
        for (c=0,i=len-1; i>=0; i--)    
            c = c*10+a[i], a[i]=c/5, c%=5;  
    }  
    return ret+ret%2*5; 
} 

欧拉函数

/*---------------------欧拉定理------------------------
  欧拉函数: 1~n中和n互素的元素个数φ(n)
  Euler定理:若gcd(a, n)=1则a^φ(n)=1 (mod n), 
  当b很大时a^b =a^(b modφ(n))(mod n)
  对于质数p, φ(p) = p-1, φ(p^n) = p^(n-1)* φ(p); 
  当(m,n)=1时,φ(mn)=φ(m)*φ(n)。
-----------------------------------------------------*/

/*==================================================*\
 |递推求欧拉函数phi(i)
\*==================================================*/ 
for (i = 1; i <= maxn; i++)    phi[i] = i; 
for (i = 2; i <= maxn; i += 2) phi[i] /= 2; 
for (i = 3; i <= maxn; i += 2) if(phi[i] == i) {
    for (j = i; j <= maxn; j += i)
        phi[j] = phi[j] / i * (i - 1);  
}

/*==================================================*\  
 |单独求欧拉函数phi(x) 
\*==================================================*/ 
unsigned euler(unsigned x) 
{// 就是公式   
    unsigned i, res=x;  
    for (i = 2; i < (int)sqrt(x * 1.0) + 1; i++) 
        if(x%i==0) {
            res = res / i * (i - 1);
            while (x % i == 0) x /= i; // 保证i一定是素数   
        }  
    if (x > 1) res = res / x * (x - 1);  
    return res;
} 

最大公约数(GCD)

/*==================================================*\
  | GCD 最大公约数 
\*==================================================*/ 
int gcd(int x, int y){  
    if (!x || !y) return x > y ? x : y;  
    for (int t; t = x % y; x = y, y = t);  
    return y; 
} 

/*==================================================*\
  | 快速 GCD 
\*==================================================*/ 
int kgcd(int a, int b){  
    if (a == 0) return b;  
    if (b == 0) return a;  
    if (!(a & 1) && !(b & 1)) return kgcd(a>>1, b>>1) << 1;  
    else if (!(b & 1)) return kgcd(a, b>>1);  
    else if (!(a & 1)) return kgcd(a>>1, b);  
    else return kgcd(abs(a - b), min(a, b)); 
}

/*==================================================*\
  | 扩展 GCD 
  | 求x, y使得gcd(a, b) = a * x + b * y;  
\*==================================================*/ 
int extgcd(int a, int b, int & x, int & y){    
    if (b == 0) { x=1; y=0; return a; }    
    int d = extgcd(b, a % b, x, y);    
    int t = x; x = y; y = t - a / b * y;    
    return d; 
} 

扩展欧几里德算法 extended-gcd

//一定存在整数x,y,使得ax+by=gcd(a,b)。
int extendedgcd (int a, int b, int&x, int& y) {
    if(!b) {
        x = 1, y = 0;
        return a;
    }
    int r = gcd(b, a%b, x, y);
    int t = x; 
    x = y; 
    y = t – a/b*y;
    return r; 
}
int extendedgcd(int a, int b, __int64 &x, __int64 &y) {
    if(!b) {
        x = 1 , y = 0;
        return  a;
    }
    int r = extendedgcd (b, a%b, y, x);
    y -= x * ( a / b );
}

组合数学相关     

/*==================================================*\
  | Polya计数 
  | c种颜色的珠子, 组成长为s的项链, 项链没有方向和起始位置;
\*==================================================*/ 
int gcd (int a, int b) { return b ? gcd(b,a%b) : a; } 
int main (void){  
    int c, s;  
    while (scanf("%d%d", &c, &s)==2) {   
        int k;   
        long long p[64]; p[0] = 1; // power of c   
        for (k=0 ; k<s ; k++) p[k+1] = p[k] * c;   
        // reflection part   
        long long count = s&1 ? s * p[s/2 + 1] : (s/2) * (p[s/2] + p[s/2 + 1]);   
        // rotation part   
        for (k=1 ; k<=s ; k++) count += p[gcd(k, s)];   
        count /= 2 * s;   
        cout << count << '\n';  
    }  
    return 0; 
} 

/*==================================================*\ 
 | 组合数C(n, r)  
\*==================================================*/ 
int com(int n, int r){// return C(n, r)  
    if( n-r > r ) r = n-r; // C(n, r) = C(n, n-r)  
    int  i, j, s = 1;  
    for( i=0, j=1; i < r; ++i ) {   
        s *= (n-i);   
        for( ; j <= r && s%j == 0; ++j ) s /= j;  
    }  
    return s; 
} 

/*==================================================*\ 
 | O(n) 求组合数  从开始从左到右递推,注意爆int
\*==================================================*/
C[0] = 1;
for(int i = 1; i <= n; i++) 
    C[i] = C[i - 1] * (n - i + 1) / i;

大数平方根

/*==================================================*\ 
 | 大数平方根(字符串数组表示) 
\*==================================================*/ 
void Sqrt(char *str) {  
    double i, r, n;  
    int  j, l, size, num, x[1000];  
    size = strlen(str);  
    if( size == 1 && str[0] == '0' ) {   
        printf("0\n");  return;  
    }  
    if( size%2 == 1 ) {   
        n = str[0]-48; l = -1;  
    } else {  
        n = (str[0]-48)*10+str[1]-48; 
        l = 0;  
    }  
    r = 0; num = 0;  

    while(true) {   
        i = 0;   
        while( i*(i+20*r) <= n ) ++i;   --i;
        n -= i*(i+20*r);   
        r = r*10+i;   
        x[num] = (int)i;   
        ++num;   
        l += 2;   
        if( l >= size ) break; 
        n = n*100+(double)(str[l]-48)*10+(double)(str[l+1]-48);  
    }  
 
    for(j = 0;j < num; j++) 
        printf("%d", x[j]);  
    printf("\n"); 
} 

错排公式

// 问题: 十本不同的书放在书架上。现重新摆放,使每本书都不在原来放的位置。有几种摆法?
// 这个问题推广一下,就是错排问题,是组合数学中的问题之一。
// 考虑一个有n个元素的排列,若一个排列中所有的元素都不在自己原来的位置上,
// 那么这样的排列就称为原排列的一个错排。 n个元素的错排数记为D(n)。 
// 研究一个排列错排个数的问题,叫做错排问题或称为更列问题。

//dp[i] = (i - 1)*(dp[i - 1] + dp[i - 2]); i > 2
ll a = 0,b = 1,c;
for (int i = 3; i <= n; i++) {
    c = ((i - 1) * 1ll * (a + b)) % MOD;
    a = b;
    b = c;
}
printf("%lld\n",c);

指数降幂公式

这里写图片描述

      计算exponial(n):这里写图片描述

#include <iostream>
using namespace std;

typedef long long ll;  
ll n,m,ans;

ll euler(ll n){ //返回euler(n) ,计算欧拉函数值
     ll res=n,a=n;  
     for(ll i=2;i*i<=a;i++){  
         if(a%i==0){  
             res=res/i*(i-1);//先进行除法是为了防止中间数据的溢出   
             while(a%i==0) a/=i;  
         }  
     }  
     if(a>1) res=res/a*(a-1);  
     return res;  
}
ll fast_mod(ll x,ll n,ll Max)  //快速幂
{  
    ll res=1;  
    while(n>0)  
    {  
        if(n & 1)  
            res=(res*x)%Max;  
        x=(x*x)%Max;  
        n >>= 1;  
    }  
    return res;  
}
ll func(ll n,ll m){      //循环求解

    if(m==1) return 0;

    if(n<=5){
        ll ans=1;
        for(int i=1;i<=n;i++){
            ans=fast_mod(i,ans,m);
        }
        return ans;
    }else{
        ll phi=euler(m);
        ll z=func(n-1,phi);
        ans=fast_mod(n,phi+z,m);
    }
    return ans;

}
void solve(){        //计算exponial(n)
    scanf("%lld%lld",&n,&m);
    printf("%lld\n",func(n,m));
}
int main(){
    solve();

    return 0;
}

母函数

普通母函数

普通母函数通常解决类似如下的问题: 给5张1元,4张2元,3张5元,要得到15元,有多少种组合? 某些时候会规定至少使用3张1元、1张2元、0张5元。 某些时候会规定有无数张1元、2元、5元

const int MAX=10000;
const int INF=0x3f3f3f3f;
//为计算结果,b为中间结果。
// K对应具体问题中物品的种类数。
//v[i]表示该乘积表达式第i个因子的权重,对应于具体问题的每个物品的价值或者权重。
//n1[i]表示该乘积表达式第i个因子的起始系数,对应于具体问题中的每个物品的最少个数,即最少要取多少个。0
//n2[i]表示该乘积表达式第i个因子的终止系数,对应于具体问题中的每个物品的最多个数,即最多要取多少个。INF
//P为可能出现的最大指数(所求情况)
int a[MAX],b[MAX],P;
int k,v[MAX],n1[MAX],n2[MAX];
//初始化a
void cal(int n)       //n为种类数
{
    memset(a,0,sizeof(a));
    a[0]=1;
    for (int i=1;i<=n;i++)//循环每个因子
    {
        memset(b,0,sizeof(b));
        for (int j=n1[i];j<=n2[i]&&j*v[i]<=P;j++)//循环每个因子的每一项
            for (int k=0;k+j*v[i]<=P;k++)//循环a的每个项
                b[k+j*v[i]]+=a[k];//把结果加到对应位
        memcpy(a,b,sizeof(b));//b赋值给a
    }
}

指数母函数:

用于求多重排列数

如以下问题: 假设有8个元素,其中a1重复3次,a2重复2次,a3重复3次。从中取r个排列,求其排列数。 对于上面的问题“假设有8个元素,其中a1重复3次,a2重复2次,a3重复3次。从中取r个排列,求其排列数。

double c1[N],c2[N];
LL fac[N];
void Fac()            //求阶乘
{
    fac[0]=1;
    for(int i=1;i<=N;i++)
        fac[i]=fac[i-1]*i;
}

int a[N];           //1~n每种的个数
void cal(int n,int m)          //有n种,取m个
{
    memset(c1,0,sizeof(c1));
    memset(c2,0,sizeof(c2));

    c1[0]=1.0/fac[0];
    for(int i=1;i<=n;i++)
    {
        for(int j=0;j<=m;j++)
            for(int k=0;k+j<=m && k<=a[i];k++)
                c2[k+j]+=c1[j]/fac[k];
        for(int j=0;j<=m;j++)
            c1[j]=c2[j],c2[j]=0;
    }
}
ans=c1[m]*fac[m];           //取m个时的多重排列数

猜你喜欢

转载自blog.csdn.net/qq_42024195/article/details/88426805