OJ 简单数学问题

最大公约数gcd和最小公倍数lcm

  • gcd(greatest common divisor)
    根据欧几里得算法,a、b的最大公约数与b、a%b的最大公约数相同;
    任何数与0的最大公约数为它本身
    因此有
int gcd(int a,int b)
{
    return !b?a:gcd(b,a%b);
}
  • lcm(lowest common multiple)
    最小公倍数可以根据最大公约数求出,为 a*b/gcd
int lcm(int a,int b)
{
    return a/gcd(a,b)*b;
}

分数运算

实现一个精简的分数类,根据题目要求实现一些必要的接口
有以下几点需要注意:

  1. 分子、分母用long long保存,防止运算时int溢出
  2. 分数化简有以下几点约定
    • 保持分母为正,如果分数是负数则令分子为负
    • 如果分子为0,令分母为1
    • 约分:分子分母同时除以gcd
  3. 除了基本+-*/操作外,还需要重载输出操作符

接口及部分实现


class fraction {

private:
    long long numerator; //定义分子  
    long long denominator;  //定义分母  


public:
    fraction(int numerator, int denominator = 1);  //构造函数  
    ~fraction();


    fraction operator+(const fraction& rv) const;  //重载 +  
    fraction operator-(const fraction& rv) const;  //重载 -  
    fraction operator*(const fraction& rv) const;  //重载*  
    fraction operator/(const fraction& rv) const;  //重载 /  
    fraction operator-();                          //重载单目运算符-  
    void reciprocal();                             //倒数  
    fraction& operator=(const fraction& rv);       //重载=  
    bool operator>(const fraction& rv) const;      //重载>  
    bool operator>=(const fraction& rv) const;     //重载>=  
    bool operator<(const fraction& rv) const;      //重载<  
    bool operator<=(const fraction& rv) const;     //重载<=  
    bool operator==(const fraction& rv) const;     //重载==  
    bool operator!=(const fraction& rv) const;     //重载!=  
private:
    void fractionReduction();                      //约分     
    int gcd(int nr, int dr);                       //求最大公约数 

};

//处理当 int 类型在双目运算符左边时的情形,在右边可由编译器自动代入构造函数  
const fraction operator+(int, const fraction&);
const fraction operator-(int, const fraction&);
const fraction operator*(int, const fraction&);
const fraction operator/(int, const fraction&);
const bool operator>(int, const fraction&);
const bool operator>=(int, const fraction&);
const bool operator<(int, const fraction&);
const bool operator<=(int, const fraction&);
const bool operator==(int, const fraction&);
const bool operator!=(int, const fraction&);
//重载输出操作符
ostream& operator<<(ostream& os, const fraction& fr);

//===============部分实现======================

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

void fraction::fractionReduction()
{
    if (denominator < 0)
    {
        numerator *= -1;
        denominator *= -1;
    }
    if (numerator == 0)
    {
        denominator = 1;
    }
    else
    {
        long long  d = gcd(abs(numerator), denominator);
        numerator /= d;
        denominator /= d;
    }
}

ostream& operator<<(ostream& os, const fraction& fr)
{
    if (fr.denominator == 1)                        //分母为1
    {
        cout << fr.numerator;
    }
    else if (abs(fr.numerator) > fr.denominator)    //假分数
    {

        cout << fr.numerator / fr.denominator << " " << abs(fr.numerator) % fr.denominator << "/" << fr.denominator;
    }
    else                                            //真分数
    {
        cout << fr.numerator << "/" << fr.fractionReduction;
    }
    return os;
}

素数

  • 判断素数

质数又称素数。一个大于1的自然数,除了1和它自身外,不能被其他自然数整除的数叫做质数
只需判断 2,3,……,floor(sqrt(n)) [包含] 之间有没有n的约数,就可以判断n是否为素数。

bool isPrime(int n)
{
    int sqrt = floor(sqrt(n));   //不必在循环的每次都计算这个值
    for(int i = 2;i <= sqrt ; ++i)
    {
        if(n%i == 0){
            return false;
        }
    }
    return true;
}
  • 素数制表
    有些题目会用到素数表,我们可以先生成一个素数表储存起来供算法使用
const int maxn = 10000;  //需要的素数的个数
int Prime[maxn];
void GetPrime()
{
    int count = 0;
    int i = 2;            //最小的素数
    while(count < maxn){  //填满maxn个素数
        if(isPrime(i)){
            Prime[count++] = i;
        }
        ++i;
    }
}

质因子分解

将一个整数写成一个或多个质数相乘的形式,形如6 = 2*3 、180 = 2^2*3^2*5,称为质因子分解。
对于质因子分解,有一条重要的理论

对于一个正整数N,如果在[2,N]存在质因子,则只有以下两种情况:

1. 所有的质因子都在 [2,sqrt(N)]
2. 有且只有一个最大的质因子大于sqrt(N),其余质因子都在[2,sqrt(N)]中


大数运算

首先了解int和long long的大致取值范围

int            约 -2x10^9 ~ 2x10^9
long long        约 -9x10^18 ~ 9x10^18

超过这个范围的称为大数

  • 储存方式
struct bigNum
{
    int length;
    int N[1000];
    bigNum(const char* s = "") :
        length(strlen(s))
    {
        memset(N, 0, 1000);
        for (size_t i = 0; i < length; i++)
        {
            N[i] = s[length - i - 1] - '0';             //倒序储存
        }
    }
};
  • 比较
    比较两个大数的大小
int compare(const bigNum& lhs, const bigNum& rhs)
{
    if (lhs.length != rhs.length)
    {
        return lhs.length > rhs.length ? 1 : -1;
    }
    else
    {
        for (size_t i = 0; i < lhs.length; i++)
        {
            if (lhs.N[lhs.length - i - 1] > rhs.N[rhs.length - i - 1])
            {
                return 1;
            }
            else if (lhs.N[lhs.length - i - 1] < rhs.N[rhs.length - i - 1])
            {
                return -1;
            }

        }
        return 0;
    }
}
  • 四则运算


    • 两个大数相加
    bigNum add(const bigNum& lhs, const bigNum& rhs)
    {
        //进位
        int carry = 0;
        bigNum res;
        for (size_t i = 0; i < lhs.length || i < rhs.length; i++)
        {
            int temp = lhs.N[i] + rhs.N[i] + carry;
            res.N[res.length++] = temp % 10;
            carry = temp / 10;
        }
        while (carry)          //处理剩下的进位值
        {
            res.N[res.length++] = carry % 10;
            carry /= 10;
        }
        return res;
    }

    • 两个大数相减,为使算法简单,保证第一个参数大于第二个参数(compare返回1),如果lhs小于rhs,则交换他们的位置,并在结果前输出负号
    //保证lhs大于rhs
    bigNum sub(bigNum lhs, const bigNum& rhs)
    {
        bigNum res;
        for (size_t i = 0; i < lhs.length || i < rhs.length; i++)
        {
            if (lhs.N[i] < rhs.N[i])
            {
                --lhs.N[i + 1];
                lhs.N[i] += 10;
            }
            res.N[res.length++] = lhs.N[i] - rhs.N[i];
        }
        while (res.length > 1 && res.N[res.length - 1] == 0)
        {
            --res.length;
        }
        return res;
    }

    • 大数乘以一个int整型数,用大数的每一位(从低位到高位)乘以整型数,然后加起来
    bigNum mul(const bigNum& ths, int small)
    {
        bigNum res;
        int carry = 0;
        int temp;
        for (size_t i = 0; i < ths.length; i++)
        {
            temp = ths.N[i] * small + carry;
            res.N[res.length++] = temp % 10;
            carry = temp / 10;
        }
        while (carry)
        {
            res.N[res.length++] = carry % 10;
            carry /= 10;
        }
        return res;
    }
    

    • 大数除以int整型数,保证除数不为0,返回 pair<商,余数>
    pair<bigNum,int> divide(const bigNum& ths, int small)
    {
        bigNum res;         //余数
        int rem = 0;
        res.length = ths.length;            //除法先生成高位(被除数也先从高位开始计算)
        for (int i = ths.length - 1; i >= 0; i--)
        {
            rem = 10 * rem + ths.N[i];
            if (rem < small)
            {
                res.N[i] = 0;
            }
            else
            {
                res.N[i] = rem / small;
                rem = rem % small;
            }
        }
        while (res.length > 1 && res.N[res.length - 1] == 0)
        {
            --res.length;
        }
        return make_pair(res, rem);
    }
    
  • 输出
    由于大数在数组中逆序(从低位到高位)排列,因此输出结果时要倒序输出

    ostream& print(ostream& os,const bigNum& out)
    {
        for(int i = out.length - 1; i >= 0; --i)
        {
            cout<<out.N[i];
        }
        return os;
    }

组合数

  • 直接计算:最大数据规模 n=20

这里写图片描述
通过公式计算组合数时特别容易溢出,例如C(21,10) = 352716,但由于 21!已结超过long long的上界而无法计算

  • 组合数递推公式计算:最大数据规模 n=67

    C(n,m)=C(n-1,m)+C(n-1,m-1)

    通过这个公式可以不断减小n,m的规模,而C(n,0)=C(n,n)=1,可以使用递归方法来求

    long long C(long long n,long long  m)
    {
        if(m==0 || m==n) return 1;
        return C(n-1,m) + C(n-1,m-1);
    }

    但这样会造成大量的重复计算
    改进:
    用一个数组将计算过的值保存起来,下次直接返回

/*该算法所能计算的范围 n不超过67*/
long long res[70][70] = {0};
long long C(long long n,long long m)
{
    if(m==0 || m==n)  return 1;
    if(res[n][m] != 0)  return res[n][m];
    return res[n][m] = C(n-1,m) + C(n-1,m-1);
}
  • 取模运算:最大数据规模 n=1000
    很多题目会要求输出组合数结果的模,mod是int型(<2*10^9)
//该算法可以计算n 1000以内的组合数
const int mod = 100000007;
int C(int n,int m)
{
    if(m==0 || m==n)  return 1;
    if(res[n][m] != 0)  return res[n][m];
    return res[n][m] = (C(n-1,m) + C(n-1,m-1)) % mod;
}

排名问题

需要注意的是:

  • 有多个人获得相同的分数
  • 相同的分数获得相同的排名

例如 100、100、95、95、90、88、85、80
排名是 1、1、3、3、5、6、7、8

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值