最大公约数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;
}
分数运算
实现一个精简的分数类,根据题目要求实现一些必要的接口
有以下几点需要注意:
- 分子、分母用long long保存,防止运算时int溢出
- 分数化简有以下几点约定
- 保持分母为正,如果分数是负数则令分子为负
- 如果分子为0,令分母为1
- 约分:分子分母同时除以gcd
- 除了基本+-*/操作外,还需要重载输出操作符
接口及部分实现
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