算法笔记 --第五章 入门篇(3) -数学问题

这里写图片描述

读书笔记

简单数学

说实话,OJ里的简单数学,其实就是考你的模拟情况。比如3n+1猜想数字黑洞等等,只要读好题,再把题目模拟出来,就OK了。这个时候就要用一些通用的模块帮助你加快编程速度了。

取位

取数字中的位是这类题目很常见的行为。根据书上的内容,我总结了二个模板
用于固定位数数字:

/*
*  把每一位存储数字里,即int num[max]  (max是位数)
* 
*/
const int max = ???;    //depends by problems
void to_array(int num[],int n){
    for(int i=0;i<max;i++)
        num[i] = n%10;
        n/=10;
    }
}
int to_number(int num[]){
    int sum = 0;
    for(int i=0;i<max;i++)
        sum = sum*10+num[i];
    return sum;
}

位于不固定位数数字:

使用C++里的stoi和itos,
或C里面的sprintf,进行int->stringstring->int

格式化输出

这类题目铁定要格式化数字
这里写图片描述
C格式化输出方法



最大公约数与最小公倍数

最大公约数

百度百科:最大公约数
欧几里得算法:
这里写图片描述

由上  递推式:gcd(a,b) = gcd(b,a%b)
     基准情况:gcd(a,0)=a
int gcd(int a,int b){
    if(b==0)
        return a;
    else
        return gcd(b,a%b);
}

最小公倍数

百度百科:最小公倍数
a,b的最小公倍数是: a b / d ,但是为了防止溢出,使用 a / d b

求n个数的最小公倍数

先求两个数的最小公约数,再用上面计算出的结果和下一个数求最小公约数
这里写图片描述

    int N;
    scanf("%d",&N);
    int a,b;
    scanf("%d %d",&a,&b);
    a = a/gcd(a,b)*b;
    while(N-->2){
        scanf("%d",&b);
        a = a/gcd(a,b)*b;
    }
    printf("%d",a);




分数的四则运算

一:分数的表示

struct Fraction{
    long long up,down;
}

①如果分数为负数,则让分子为负数,分母为正数
②如果分数为0,则让分子为0,分母为1
③分数为假分数
④分数要求最简(即分子和分母没有除了1以外的公约数)

二:分数的化简

只要是按照上面四个规则化简

Fraction reduction(Fraction result){
    if(result.down < 0){
        result.up = -result.up;
        result.down = -result.down;
    }
    if(result.up ==0){
        result.down =1;
    }else{
        int d = gcd(abs(result.up),abs(result.down));
        result.up/=d;
        result.down /=d;
    }
    return result;
}

加法

这里写图片描述

Fraction add(Fraction f1,Fraction f2){
    Fraction result;
    result.up = f1.up*f2.down + f2.up*f1.down;
    result.down = f1.down *f2.down;
    return reduction(result);
}

减法

这里写图片描述

Fraction minu(Fraction f1,Fraction f2){
    Fration result;
    result.up = f1.up*f2.down -f2.up*f1.down;
    result.down = f1.down*f2.down;
    return reduction(result);
}

乘法

这里写图片描述

Fraction multi(Fraction f1,Fraction f2){
    Fraction result;
    result.up = f1.up*f2.up;
    result.up = f1.down*f2.down;
    returnd reduction(result);
}

除法

这里写图片描述
注意 如果f2.up为0,那么应该返回Inf

Fraction divide(Fraction f1,Fractiojn f2){
    Fraction result;
    result.up = f1.up*f2.down;
    result.down = f1.down*f2.up;
    return reduction(result);
}

输出

①输出前应该化简
②如果down为1,则直接输出up。(此时为整数)
③如果up>down,则应该带分数。
④原样输出

void display(Fraction r){
    r = reduction(r);
    if(r.down ==1)
        printf("%lld",r.up);
    else if(abs(r.up) > r.down)
        printf("%d %d/%d",r.up/r.down,abs(r.up)%r.down,r.down);
    else
        printf("%d/%d",r.up,r.down);
}




素数

百度百科: 素数

判断素数

bool isprime(int n){
    if(n<=1)
        return false;
    int Sqrt =(int)sqrt(1.0*n);
    for(int i=2;i<=Sqrt;i++)
        if(n%i==0)
            return false;
    return true;
}

可能溢出的版本:

bool ismrime(int n){
    if(n<=1)
        return false;
    for(int i=2;i*i<=n;i++)
        if(n%i==0)
            return false;
    return true;
}

埃式筛法

const int max = ??;
int find_prime(int *prime)
{
    bool p[max]={0};
    int j=0;
    for(int i=2;i<max;i++){
        if(p[i] == false){
            prime[j++]=i;
            for(int k=i+1;k<maxl;k+=i)
                p[k]=true;
        }   
    }
    return j;
}




质因子分解

百度百科:质因子
质因子的结论: 对于一个正整数n来说,如果它存在 [ 2 , n ] 范围内的质因子,要么这些质因子全部小于等于 n 。要么只存在一个大于 n 的质因子,而其余质因子全部小于 n

/*
*   prime[i] 素数表     pNum是素数表中素数的个数
*/
struct factor{
    int x,cnt;
}fac[10];
int num=0;
int Sqrt = (int)sqrt(1.0*n);
for(int i=0;i<pNum && prime[i]<=Sqrt;i++){
    if(n%prime[i] ==0){
        fac[num].x=prime[i];
        fac[num].cnt=0;
        while(n%prime[i] ==0){
            fac[num].cnt++;
            n/=prime[i];
        }
    }
    if(n==1) break;
}
if(n!=1){
    fac[num].x=n;
    fac[num++].cnt=1;
}

求一个正整数N的因子个数

设N的质因子为 p 1 , p 2 , p 3 . . . . . p n ,且各质因子 p i 的个数分别为 e 1 , e 2 , e 3 . . . . e n ,那么N的因子个数为 ( e 1 + 1 ) ( e 2 + 1 ) . . . . . ( e n + 1 ) .
N的因子个数是由N的质因子个数乘积得来的。每个质因子出现的次数为 0 , 1 , 2...... e i ,所以结果为 ( 1 + e i )
所有因子出现0次,即结果为1。(1也是因子)



大整数运算

大整数的存储

大整数的存储是把每一位倒叙放在数组里。例 65536 , d [ 0 ] = 6 , d [ 1 ] = 3 , d [ 2 ] = 5 , d [ 3 ] = 5 , d [ 4 ] = 6

struct bign{
    int d[1000];
    int len;
};

大整数的读入

先用字符串读入大整数,再把字符串转换成 b i g n 结构体

bign change(char str,int len){
    bign a;
    for(int i=0;i<len;i++)
        a.d[i]=str[len-1-i]-'0';
    return a;
}

大整数的大小比较

先比较长度,再每一位比较

int compare(bign a,bign b){
    if(a.len >b.len)
        return 1;
    else if(a.len == b.len){
        for(int i=a.len-1;i>=0;i--){
            if(a.d[i] >b.d[i])
                return 1;
            else uf(a.d[i] < b.d[i])
                return -1;
        }
        return 0;
    }
    else
        return -1;
}

大整数的四则运算

bign add(bign a,bign b){
    bign c;
    int carry=0;
    for(int i=0; i<a.len || i<b.len ;i++){
        int temp = a.d[i]+b,d[i]+carry;
        c.d[i] = temp%10;
        carry = temp/10;
    }
    if(carry !=0)
        c.d[c.len++] =carry;
    return c;
}

大整数减法

如果减数大于被减数,则要交换

bign sub(bign a,bign b){
    bign c;
    for(int i=0; i<a.len;i++){
        if(a.d[i] <b.d[i]){
            a.d[i+1]--;
            a.d[i]+=10;
        }
        c.d[c.len++]=a.d[i]-b.d[i];
    }
    while(c.len-1 >=1 && c.d[c.len-1]==0)
        c.len--;
    return c;
}

大整数乘法

bign multi(bign a,int b){
    bign c;
    int carry=0;
    for(int i=0;i<a.len;i++){
        int temp=a.d[i]*b+carry;
        c.d[c.len++] = temp%10;
        carry = temp/10;
    }
    while(carry !=0){
        c.d[c.len++]=carry%10;
        carry/=10;
    }
    return c;
}

大整数除法

bign divide(bign a,int b,int& r){
    bign c;
    c.len = a.len;
    for(int i=a.len-1;i>=0;i--){
        r=r*10+a.d[i];
        if(r<b)
            c.d[i]=0;
        else{
            c.d[i]=r/b;
            r = r%b;
        }
    }
    while(c.len-1>=1 && c.d[c.len-1]==0)
        c.len--;
    return c;
}




扩展欧几里得算法

一:base problem

给定两个非负整数a和b,求一组整数解(x,y),使得ax+by=gcd(x,y)成立.
这里写图片描述
这样就可以根据递推式得出代码:
base case : a = a , b = 0 x = 1. y = 0

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

这里写图片描述
这样,就能求出所有的解了。而且x的解是以 b / g c d 为周期,y的解是以 a / g c d 为周期.
x的最小解为 ( x % b / g c d + b / g c d ) % b / g c d
这里写图片描述

二: update level problem Ⅰ

求解 a x + b y = c 的解
由下可得:存在解的从要条件为 c%gcd==0$
这里写图片描述
由上,我们便可以推导出所有解了
x = c x 0 g c d + b g c d n
y = c y 0 g c d a g c d n

update level problem Ⅱ

这里写图片描述
便变成上面的问题了,注意 c%gcd(a,m)才有解

update level problem ⅲ

逆元的求解和(b/aa)%m的计算.
百度百科:逆元
这里写图片描述
由上,可以得求逆元的方法:
如果 g c d ( a . m ) ! = 1 ,没有逆元
如果 g c d ( a , m ) = 1 ,则逆元为(x%m+m)%m(先求的其中一个解x,再求出最小值)

int inverse(int a,int m){
    int x,y;
    int g = exGcd(a,m,x,y);
    return (x%m+m)%m;
}

组合数

组合数的计算

这里写图片描述
如果老老实实的按照定义计算,很容易溢出。

使用递归公式计算

这里写图片描述
要么从n-1个数中选择m种组合,要么从n-1个数中选择m-1种组合。

long long res[64][64]={0};
const int n =60;
long long C(){
    for(int i=0;i<=n;i++){
        res[i][0] = res[i][i] =1 ;   //base case
    }
    for(int i=2;i<=n;i++){
        for(int j=0;j<=n;j++){
            res[i][j] =res[i-1][j]+res[i-1][j-1];
            res[i][i-j]=res[i][j];   
        }
    }
}

使用定义式变形计算

这里写图片描述

long long C(long long n,long long m){
    long long ans = 1;
    for(long long i=1;i<=m;i++)
        ans = ans*(n-m+i)/i;
    return ans;
}

C n m % p 求解

case 1

该方法是基于递推公式得到,即在每一轮递推都%p

long long res[64][64]={0};
const int n =60;
long long C(){
    for(int i=0;i<=n;i++){
        res[i][0] = res[i][i] =1 ;   //base case
    }
    for(int i=2;i<=n;i++){
        for(int j=0;j<=n;j++){
            res[i][j] =res[i-1][j]+res[i-1][j-1]%p;
            res[i][i-j]=res[i][j];   
        }
    }
}

more case

书上介绍了更多方法,我有些晕了

n! problem

求n!中有多少个质因子p
n!有 n p + n p 2 + n p 3 + n p 4 . . . . . . 个质因子p

int cal(int n,int p){
    int ans = 0;
    while(n){
        ans+=n/p;
        n/=p;
    }   
    return ans;
}

推导出的新结论:求n!中末尾0个个数:是n!中因子10的个数,而又等于质因子5的个数,即为
cal(n,5)



完结撒花d=====( ̄▽ ̄*)b

这里写图片描述

猜你喜欢

转载自blog.csdn.net/weixin_41256413/article/details/81626090
今日推荐