[C++] 算法学习笔记汇总(一、数学相关及一些较为基础的知识)

 一、辗转相除法(欧几里得算法):解决快速计算最大公约数

思想:gcd(a,b)=gcd(b,a%b) (a≥b)  我自己的理解是:设x=ab任意约数 a=kb+r x|a x|b x|a-k*b x|r 最大公约数当然也如此,因此可借助此性质缩小范围

#define ull unsigned long long
ull gcd(ull a,ull b){ return (b==0)?a:gcd(b,a%b);  }

二、扩展欧几里得:解不定方程,求解线性同余方程,求解模的逆元等

这个主要解决ax+by=c的(最小正)整数解问题

第一步:解决ax+by=gcd(a,b)的整数解问题 (给出一组即可)

先考虑求一个规模比较小的答案 再反推 参考gcd的思路:

可以再列一式:bx'+(a%b)y'=gcd(b,a%b) 整数解 

由gcd(a,b)=gcd(b,a%b) 等量代换得ax+by=bx'+(a%b)y'  我们看到了在x与x'中建立联系的可能!

整理:bx'+(a%b)y' =bx'+(a-[a/b]*b)y' =bx'+ay'-[a/b]*b*y'=ay'+b(x'-[a/b]*y')=ax+by

所以假设我们知道x' y'   则x=y' y=x'-[a/b]*y'

结束情况: 同gcd  bx'+0y'=gcd(b,0)时 返回x'=1 y'=0


9x+6y=gcd(9,6)  x=1 y=0-[9/6]*1=-1   ^  验:9-6=3
6x'+3y'=gcd(6,3) x'=0 y'=1-[6/3]*0=1   |
3x''+0y''=gcd(3,0) x''=1 y''=0                |

第二步:解决ax+by=c的整数解问题 (给出一组即可)

由ax+by=gcd(a,b)左右同乘c/gcd(a,b)可得

a(x*c/gcd(a,b))+b(y*c/gcd(a,b))=c

在原来的解的基础上乘个c/gcd(a,b)即可
(当c%gcd(a,b)!=0时无解)

第三步:解决ax+by=c的最小正整数解问题 

根据唯一分解定理可证   lcm=a*b/gcd (lcm是最小公倍数的意思)
因为   gcd是ab里面每个质数取指数小的那个    lcm是ab里面每个质数取指数大的那个  
假设得到ax+by=c一组解
ax+lcm(a,b)+by-lcm(a,b)=c
ax+a*b/g+by-a*b/g=c
a(x+b/g)+b(y-a/g)=c
由是 可以通过一组解的x 加或减 b/g 求其他的x y同理
用来出最小正整数解 。

#include<bits/stdc++.h>
using namespace std;
int exgcd(int a,int b,int &x,int &y){
	if(b==0){ x=1; y=0; return a; } 
    else{ int gcd=exgcd(b,a%b,x,y); swap(x,y); y-=a/b*x; return gcd;} 
}//给出ax+by=gcd(a,b) 一组整数解 
void solveMpis(int a,int b,int c,int &x,int &y){
	int gcd=exgcd(a,b,x,y);  if(c%gcd){ x=y=-0x3f; return;} 
	x*=c/gcd; y*=c/gcd; 
	while(x>0){ x-=b/gcd; y+=a/gcd; }  while(x<=0){ x+=b/gcd; y-=a/gcd; } return;
}//求解ax+by=c 最小正整数解 */
int main(){
	int a,b,c,x,y; scanf("%d%d%d",&a,&b,&c); 
	solveMpis(a,b,c,x,y);
	if(x>0){
		printf("%dx+%dy=%d的最小正整数解:\nx=%d,y=%d:",a,b,c,x,y);
	}
	else{
		printf("%dx+%dy=%d无整数解\n",a,b,c);
	}
	return 0;
}

三、快速幂

思想:在二进制的基础上把指数拆开  例如把a^11分成a^1*a^2*a^8 (11的二进制是1011)

不断把a乘方准备着 另外把指数的每一个二进制位过一遍 如果到了二进制位是1的时候 就把此时的a乘进去 

#define ull unsigned long long
ull fastPow(ull a,ull b,ull mod){ //a^b%mod
    ull ret=1; 
    while(b){ 
        if(b&1) ret=ret*a%mod; //判断指数最后一位是0是1 因为b和0001按位与 前面的都一定成了0 关键在于最后一位
        a=a*a%mod; //a不断平方
        b>>=1;//把b整体在二进制右移1位 移完为止
    }                         
    return ret; 
}

注意取模,不要爆int或爆long long

四、逆元

逆元:即取模意义下的倒数:即在mod p 下 使得a/b=a*inv(b); 
已知a,p 求mod p时a的逆元:

法一:转化为扩展欧几里得的应用 (要求a,m互素)

ax=1 mod p 则 ax mod p=1 即ax/p=k...1 因此求ax-kp=1     

法二:或费马小定理&快速幂
a^(p-1)=1 mod p   (p为质数)

则 a*a^(p-2)=1 mod p   逆元即为a^(p-2)  

(一般出的题目要取模的都是个挺大的质数)

int main(){
	int a,p,k,x; scanf("%d%d",&a,&p); 
	solveMpis(a,p,1,x,k);
	printf("模%d时%d的逆元:%d\n",p,a,x);
	printf("模%d时%d的逆元:%d\n",p,a,fastPow(a,p-2,p));
	return 0;
}
//其中扩欧和快速幂部分参见上文

五、埃氏筛&欧拉筛 解决快速完成质数筛选(关于质数有兴趣的还可以了解Miller-Rabin算法)

思想:

埃氏筛:

我们用一个bool数组notPrime[],norPrime[i]==true则i非质数 再来一个int数组prime[]存下筛到的质数 从2到n 我们每找到一个素数就把他存到prime里 并把他的2,3,4...倍都在notPrime[]置为true

可以做的优化:我们发现小于x*x的x的倍数在之前已经被筛掉了 例如9的8倍在8的9倍时肯定已经筛过了 所以内层for循环可以省去一部分

线性筛:

埃氏筛依然有缺点 即仍然会重复多次筛掉一个数

解决方法:让每个数只能被他最大的因数筛掉

我们来看内层for循环,对于每一个数i都去筛他的质因数倍到prime[j] | i时停下!!!

原因:prime[]存储的是i之前筛出来的质数 就一个比i小的质数*i 乘出来的被筛掉的数最大约数一定是i prime[j] | i说明一定有因数比i还大了 ,那之后i无论再乘什么质数,出来的合数的最大因数肯定不是i了 直接break

*或者这么说,prime[j] | i 那不妨设 i=k*prime[j] 再乘一个更大的质数=k*prime[j]*prime[p]

则显而易见 k*prime[p]>i

#define ull unsigned long long
const ull maxn=1e9+1; const int maxm=1e5+1; 
bool notPrime[maxn]={0}; int prime[maxm]={0}; int cnt=0;
void Eratosthenes_prime(ull n){
	memset(notPrime,0,sizeof(notPrime));
	notPrime[1]=true;
	for(ull i=2;i<=n;i++){
		if(notPrime[i]) continue;
		prime[++cnt]=i;
		for(ull j=i;j*i<=n;i++) notPrime[i*j]=true;
	}
}
#define ull unsigned long long
const ull maxn=1e9+1; const int maxm=1e5+1; 
bool notPrime[maxn]={0}; int prime[maxm]={0}; int cnt=0;
void Euler_prime(ull n){
	memset(notPrime,0,sizeof(notPrime));
	notPrime[1]=true;
	for(ull i=2;i<=n;i++){
		if(!notPrime[i]) prime[++cnt]=i;
		for(ull j=1;j<=cnt&&prime[j]*i<=n;j++){
			notPrime[prime[j]*i]=1;
			if(i%prime[j]==0) break;
		}
	}
	return;
}

六、经典的球放盒问题,n个球放m个盒的可能性(组合数学&递推)

*1球同盒同可空:按有没有空的分类讨论 F(n,m)=F(n,m-1)+F(n-m,m)

2球同盒同无空:同1联系起来 f(n,m)=F(n-m,m)

*3球同盒不同无空: 隔板法  \mathbf{​{C}_{n-1}^{m-1}}

4球同盒不同可空: {C}_{n+m-1}^{m-1}

*5球不同盒同无空: 按最后一球是不是单独一盒讨论:DP[n][m] = m*DP[n-1][m]+DP[n-1][m-1] 

6球不同盒同可空 :枚举盒子的个数\sum_{i=1}^{n}DP[n][i]

7球不同盒不同可空 每一个球都有m种选择 m^{n}

8球不同盒不同无空 在5的基础上排列箱子 DP[n][m]*m!

七、快读快写 解决大量输入输出

 快读

#define ll long long
inline ll read(){
    register ll x=0; int f=1;//标记正负 
    register char c=getchar(); 
	// 读入单个字符到cpu寄存器 (register 声明变量只能在主函数或自定义内部)
    while(c<'0'||c>'9'){
        if(c=='-') f=-1;
        c=getchar();
    }//先把数字之前的符号位等处理一下 
    while(c>='0'&&c<='9'){
        x=(x<<1)+(x<<3)+(c^48);  // 移位与异或 记住,括号就是这么加的!
      	//等价于 x = x * 10 + ch - '0'
        c=getchar();
    }
    return f*x;
}

快写 

inline void write(ll x){
	if(x<0){
		putchar('-'); x=-x;
	}
	if(x>9) write(x/10);
	putchar(x%10+'0');
} 
//这里还不能轻易地用while循环 因为要考虑输出的顺序 所以用递归 

附:部分参考资料

球放入盒的组合问题_放球入盒的排列组合问题-CSDN博客

https://www.cnblogs.com/zjp-shadow/p/9267675.html#代码实现-3

https://www.cnblogs.com/crashed/p/13755082.html

《组合数学全家桶》(ACM / OI 全网最全,清晰易懂)_信息学奥赛 数学全家桶-CSDN博客

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值