一、辗转相除法(欧几里得算法):解决快速计算最大公约数
思想: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球同盒不同无空: 隔板法
4球同盒不同可空:
*5球不同盒同无空: 按最后一球是不是单独一盒讨论:DP[n][m] = m*DP[n-1][m]+DP[n-1][m-1]
6球不同盒同可空 :枚举盒子的个数DP[n][i]
7球不同盒不同可空 每一个球都有m种选择
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循环 因为要考虑输出的顺序 所以用递归
附:部分参考资料
https://www.cnblogs.com/zjp-shadow/p/9267675.html#代码实现-3