一、质数(素数)
欧拉筛:
每个数只被筛一次,可以避免重复删
a 记录是否为质数,b存储全部质数
y=0;
for(int i=2;i<=q;i++)
{
if(a[i]==0) b[y++]=i;
for(int j=0;b[j]*i<=q;j++) //质数的i倍
{
a[b[j]*i]=1; //不是质数
if(i%b[j]==0) break; //及时截至
}
}
二、组合数(Cnm)
1、杨辉三角:
for(int i=0;i<=40;i++)
{
for(int j=0;j<=i;j++)
{
if(j==0) a[i][j]=1;
else a[i][j]=a[i-1][j-1]+a[i-1][j]; //Cmn = Cmn−1 + Cm−1n−1
}
}
还有他的兄弟,计算Amn
sum=1;
m=n-m+1;
for(int i=m;i<=n;i++)
{
sum*=i;
sum%=p;
}
2、lucas 定理:
对于质数 p 以及正整数 m > n 在 p 进制 下:
m = a0 + a1 * p + …… + ak * p ^ k
n = b0 + b1 * p + …… + bk * p ^ k
则有:
ull qmi(ull a, ull b, int p)//快速幂
{
ull x=1;
while (b)
{
if (b & 1) x = (ull)x * a % p;
a = (ull)a * a % p;
b>>=1;
}
return x;
}
int C(int a, int b, int p) // 通过定理求组合数C(a, b)
{
if (a < b) return 0;
ull x = 1, y = 1; // x是分子,y是分母
for (int i = a, j = 1; j <= b; i --, j ++ )
{
x = x * i % p;
y = y * j % p;
}
return x * qmi(y, p - 2, p) % p;用到费马小定理
}
ull lucas(ull a, ull b, int p)
{
if (a < p && b < p) return C(a, b, p);
return (ull)C(a % p, b % p, p) * lucas(a / p, b / p, p) % p;
}
三、约数
1、每个数都是由几个质数(2,3,5,7,11,13,17,19,21,23 ……)相乘得到的
例:20 = (2 ^ 2) *( 5 ^ 1)
2、 约数个数:
假设有一个数N = 2 ^ x * 3 ^ y * 4 ^ z * ……
即 共有 x 个 2 ,y 个3 ……
每次可选0,1,2,,,x 个 2 ,同理也可选 0 - y 个3 …… (每种不同的选法都对应一个约数)
所有的约数个数是:(x+1) (y+1)(z+1)
约数之和:(2 ^ 0 + 2 ^ 1 + 2 ^ 2 ……+2 ^ x)( 3 ^ 0 + 3 ^ 1 + 3 ^ 2 ……+3 ^ y)(4 ^ 0 + 4 ^ 1 + 4 ^ 2 ……+ ^ z)……
n个学生中挑出k个人使得能力值的最大公约数最大//计算能整除自身的个数
每头奶牛轮流走上一圈,找到手上数字能整除在自己纸条上的数字的牛的头的个数,//计算自身能整除的个数
都是先求出最大数,并且存当前这个数共有几个
循环 i ,求出能整除 1-max 的个数(即求能把 i 作为因子的所有个数)
for(int i=1;i<=n;i++)
{
cin>>x;
b[x]++;
m=max(m,x);
}
for(int i=1;i<=m;i++)
{
//自身能整除
if(b[i])
{
for(int j=1;j*i<=m;j++)
{
c[j*i]+=b[i];
}
}
// 能整除自身
for(int j=1;j*i<=m;j++) //能整除i的,统计个数
{
sum+=b[j*i];
}
}
四、模运算
商 * 除数+余数=被除数
(a+b) % m = ( (a % m ) + (b % m) ) % m;
(a - b) % m = ( (a % m ) - (b % m) ) % m; // (a - b) % m = ( (a % m ) - (b % m) + m ) % m //保证结果为正数
(a * b) % m = ( (a % m ) * (b % m) ) % m;
没有除法;
负进制数:
为了保证余数不出现负数 : 余数-除数(减去负的为正),商+1
(商+1) * 除数 +(余数-除 数) < = > 商 * 除数 + 余数
举例: -15 = 7 * (-2)+ -1 // -15是7个-2 余 -1
让余数减去一个 -2 ,即余 1 时,就要变成 8 个 -2 ,才能让式子相等
五、快速幂
当求 a^n 的 n 比较大时
原理:a ^ n +a ^ m =a ^ m+n
快速幂 + 快速幂取模
while(b>0)
{
if(b%2==1)//如果是奇数就多乘一次
{
x *= a;
//x %= p;
}
a*=a;
//a%=p;
b=b/2;最后一定会变成 1
}
//y=x%p;
六、最大公约数 , 最小公倍数
1、GCD、LCM
1、GCD :
性质:
①、可以用来约分
②、gcd (a,0) = a ;
- 公式: __gcd(x,y)
- 辗转相除法
c=a%b; while(c!=0) { a=b; b=c; c=a%b; } cout<<b<<endl;
2、LCM
x = a / gcd(a,b) * b;
2、拓展欧几里得
a* x + b* y = gcd(a,b) 求解 x0,y0
原始简化得 : c * x + d *y =1 //c = a / (gcd(a,b)) , d = b / (gcd(a,b)) , c d互质
然后得通解 :
x = x0 + d * t
y = y0 - c * t // t 为任意数
void extend_gcd(int a,int b)
{
if(b==0)
{
x=1,y=0;
return;//x=1,y=0,a∗1+b∗0=gcd(a,b)此时的a就是gcd(a,b)
}
extend_gcd(b,a%b);
//此时的x和y已经是回溯后的了
int temp = x;
x = y;
y = temp -(a/b)*y;
}
int main()
{
cin>>a>>b;
x=0; y=0;
extend_gcd(a,b);
return 0;
}
七、位运算
1、与1异或,可以使特定位翻转 ,与0异或,保留其值 0101 0101 ^ 1111 0000 = 1010(翻转) 0101(保留)
2、1除了最低位,其他位都为0,所以按位与结果取决于n最后一位,如果n最后一位是1,则结果为1,反之结果为0
通常用来判断当前位是否为 1
3、0的二进制是: 00000000……… -1的二进制是:1111111111…………
只用0和-1进行位运算,就可以得到任何一位的任何情况进行位运算的结果
4、k 个相同的数的异或和,当 k 为奇数时,结果是这个数本身,否则结果是 0 。
八、求解逆元
前面的模运算说过有加减乘的模运算,但是除法却不符合,逆元就是解决此类问题
即,用于求 ( a / b ) mod p
所以对于( a / b )mod p ,我们就可以求出 b 在 mod p 意义下的逆元,然后乘上 a ,再 mod p ,就是这个乘法逆元的值了
1、拓展欧几里得
x=0;y=0;
extend_gcd(i,b);
x=(x%b+b)%b;
2、快速幂+费马小定理
费马小定理: 如果 a,p 互质,那么 a ^(p-1) ≡ 1 ( m od p )
结合逆元方程 :a ∗ x ≡ 1 ( mod p ) ,得到 a ∗ x ≡ a ^(p−1) * (mod p)
根据同余的性质 ,若 p 为质数,得到 x ≡ a ^ ( p - 2 ) * (mod p) , 然后快速幂 求解即可
scanf("%lld%lld",&n,&b);
x=b-2; y=1;
a;
while(x>0)
{
if(x%2==1)//如果是奇数就多乘一次
{
y *= a; y %= b;
}
a*=a; a%=b;
x=x/2;
}
y=y%b;
printf("%lld\n",y);
3、线性算法 (复杂度 O(n))
如果要解模的逆元的数 , 数量很多,但是连续 , 就可以使用递推法 。
scanf("%lld%lld",&n,&b); //当时的题时间卡的死
a[0]=0;
a[1]=1;
printf("1\n");
for(i=2;i<=n;i++)
{
a[i]= (long long)(b-b/i)*a[b%i]%b;
printf("%lld\n",a[i]);
}
九、特殊计数(递推公式)
1、斐波那契数列 :从第三项开始,每一项都等于前两项之和
f ( 1 ) = f ( 2 ) = 1
f ( n ) = f ( n - 1 ) + f ( n - 2 )
2、卡特兰数 C2n n / (n+1)
卡特兰数:1,1,2,5,14,42,132,429……
用来计算是否合法等问题
①:棋盘问题:一直在原对角线右下方,不穿过主对角线,路径数有多少
②:进栈出栈,有进才有出,进的一定比出的多,就是合法的进栈出栈总操作个数
③:括号问题:一串字符串只有" )" “(” 组成,找合法组合,共有多少组合方式
④:二叉树:n个结点能构成多少种二叉树
⑤:一个凸多边形能化成多少个三角形
3、斯特林数
1、第一类斯特林数:把n个不同元素分配到 k 个圆排列里,圆不能为空,有多少种分法:
s ( n , k ) = s ( n - 1 , k - 1 ) + ( n -1 ) * s ( n - 1 , k ) ,
s ( 0 , 0 ) = 1 , s ( k , 0 ) = 0
s ( n , n ) = 1 , s( n , 1 ) = ( n - 1 ) !
2、第二类斯特林数:把 n个不同的球分配到 k个相同的盒子里,不能有空盒子,有多少种分法:
s ( n , k ) = s ( n - 1 , k - 1 ) + k * s ( n - 1 , k ) ,
s ( 0 , 0 ) = 1 , s ( i , 0 ) = 0
例 2.1 :要将 n 个互不相同的球放入 k 个互不相同的盒子中,且不允许有空盒子, 有多少种不同的分法:
cin>>n>>m;
for(int i=0;i<=n;i++) //初始化
{
f[i][0]=0;
}
f[0][0]=1;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=i;j++)
{
f[i][j]=j*f[i-1][j]+f[i-1][j-1];
}
}
sum=1;
for(int i=1;i<=m;i++) //因为互不相同,所以要乘上Amm
{
sum*=i;
}
cout<<f[n][m]*sum<<endl;
十、错排
排列n个数,每个数都不在自己原本的位置上
已知 D1 = 0 , D2 = 1。
当 n=3 时:
设数 1 放在位置 2,那么,对于数字 2,它有两种情况:
①:数字 2 放到位置 1,那么就相当于 3 到 i 的排列中 m=0 的排列数,显然有D i−2 种排列数。
②:数字 2 不放到位置 1,那么就相当于 2 到 i 的排列中 m=0 的排列数,显然有 D i−1 种排列数。
递推公式:
d [ 1 ]=0 ,d[2] = 1
d [ i ] = ( i - 1 ) * ( d [ i - 1 ] + d [ i - 2 ] ) (i>=3)