读书笔记
简单数学
说实话,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->string和string->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的最小公倍数是:
,但是为了防止溢出,使用
求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来说,如果它存在
范围内的质因子,要么这些质因子全部小于等于
。要么只存在一个大于
的质因子,而其余质因子全部小于
/*
* 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的质因子为
,且各质因子
的个数分别为
,那么N的因子个数为
.
N的因子个数是由N的质因子个数乘积得来的。每个质因子出现的次数为
,所以结果为
。
所有因子出现0次,即结果为1。(1也是因子)
大整数运算
大整数的存储
大整数的存储是把每一位倒叙放在数组里。例 ,
struct bign{
int d[1000];
int len;
};
大整数的读入
先用字符串读入大整数,再把字符串转换成 结构体
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 :
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的解是以
为周期,y的解是以
为周期.
x的最小解为
二: update level problem Ⅰ
求解
的解
由下可得:存在解的从要条件为 c%gcd==0$
由上,我们便可以推导出所有解了
update level problem Ⅱ
便变成上面的问题了,注意 c%gcd(a,m)才有解
update level problem ⅲ
逆元的求解和(b/aa)%m的计算.
百度百科:逆元
由上,可以得求逆元的方法:
如果
,没有逆元
如果
,则逆元为(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;
}
求解
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!有
个质因子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)