文章目录

  • 质数:在大于1的整数中,如果只包含1和本身这两个约数,为质数,或者叫做素数。
  • 约数
  • 互质与欧拉函数
  • 快速幂
  • 欧几里得
  • 博弈论入门

质数:在大于1的整数中,如果只包含1和本身这两个约数,为质数,或者叫做素数。

  1. 质数的判定——试除法: 时间复杂度: 算法课_第四章_数学知识_ci
bool is_prime(int n)
{
   	if(n < 2) return false;
   	//for(int i = 2; i * i <= n; ++i) //i会溢出,变成负数造成错误
   	for(int i = 2; i <= n/i; ++i)
   		if(n % i == 0)
   			return false;
   	return true;
}
  1. 分解质因数——从小到大枚举所有数: 时间复杂度:算法课_第四章_数学知识_ci
void divide(int n)
{
//从小到大枚举所有数
//n中只包含一个大于sqrt(n)的质因子,反正即可
for(int i = 2; i <= n/i; ++i)
{
	if(n % i == 0)
	{
		int s = 0;	//次数
		while(n % i == 0)
		{
			n /= i;
			s++;
		}
		printf("%d %d\n", i, s);  //
	} 
}
if(n > 1) printf("%d %d\n", n, 1);  //如果最后>1 则该数即为 大于sqrt(n)的质因子
puts(" ");
}
  1. 埃氏筛法——枚举1~n的所有质数的倍数:时间复杂度: 算法课_第四章_数学知识_时间复杂度_03
void get_prime(int n)
{
	for(int i = 2; i <= n; ++i)
	{
		if(!st[i])
		{
			primes[cnt++] = i;
			for(int j = i+i; j <= n; j += i) st[j] = true;
		}
	}
}
  1. 线性筛法——用该数的最小质因子筛去时间复杂度: 算法课_第四章_数学知识_gcd_04
void get_primes(int n)
{
    for(int i = 2; i <= n; ++i)
    {
        if(!st[i])
        {
            primes[cnt++] = i;
        }
        for(int j = 0; primes[j] <= n/i; j++)
        {
            cout<<primes[j]<<' '<<i<<" ---> "<<primes[j]*i<<endl;
            st[primes[j]*i] = true;
            //这行代码神奇地保证了每个合数只会被它的最小素因子筛掉,把复杂度降到了O(N)。
            if(i % primes[j] == 0) break;
        }
    }
}
prime[]数组中的素数是递增的,当i能整除prime[j],那么i*prime[j+1]这个合数肯定被prime[j]乘以某个数筛掉。
因为i中含有prime[j],prime[j]prime[j+1]小,即i=k*prime[j],那么i*prime[j+1]=(k*prime[j])*prime [j+1]=k’*prime[j],接下去的素数同理。所以不用筛下去了。因此,在第一次满足i%prime[j]==0这个条件
时,prime[j]必定是prime[j]*i的最小因子。

算法课_第四章_数学知识_数据结构_05


参考:



约数


  1. 试除法求一个数的所有约数——一个数的约数是成对出现的:时间复杂度:算法课_第四章_数学知识_ci_06
vector<int> get_divisor(int n)
{
    vector<int> res;
    	
    for(int i = 1; i <= n/i; ++i)
    {
    	if(n % i == 0)
    	{
    		res.push_back(i);
    		if(i != n/i) res.push_back(n/i);  //防止i*i为n,只要一个
    	}
    }
    sort(res.begin(), res.end());
    return res;
}
  1. 约数个数
  2. 约数定理
  3. 最大公约数——欧几里得算法
/*
a >= b 时成立
a < b && b != 0时 gcd(a, b) == gcd(b, a%b) == gcd(b, a)成立 
*/
int gcd(int a, int b)  
{
	return b ? gcd(b, a%b) : a;
}

互质与欧拉函数

互质:除了1以外,没有其他公约数时,称这两个数为互质数.
欧拉函数:1~N中与N互质的数的个数


  1. 公式求欧拉函数:时间复杂度:算法课_第四章_数学知识_ci_07
int get_ola(int x)
{	
	int res = x;
	for(int i = 2; i <= x/i; ++i)
		{
			if(x % i == 0)
				{
					res = res / x * (x-1); //因为只按照公式直接干的话,直接乘会越界,但是先除就不会越界
					while(x % i == 0) x /= i;
				}
		}
	if(x > 1) res = res / x * (x-1);
	cout<<res<<endl;
}
  1. 线性筛法求欧拉函数:时间复杂度:算法课_第四章_数学知识_gcd_04


LL get_ola(int n)
{
	phi[1] = 1;
	for(int i = 2; i <= n; ++i)
	{
		if(!st[i]) 
		{
			primes[cnt++] = i;
			phi[i] = i-1; //如果一个数i是质数的,其欧拉函数就是i-1个//1、
		}
		for(int j = 0; primes[j] <= n/i; ++j)
		{
			st[primes[j]*i] = true;
			if(i % primes[j] == 0) //break;
			{
				phi[primes[j]*i] = primes[j]*phi[i]; // 2、
				break;
			}
			else
			{
//				phi[primes[j]*i] = primes[j]*phi[i] / primes[j] * (primes[j]-1);
				phi[primes[j]*i] = phi[i] * (primes[j]-1);	//3、
				break;
			}
		}
	}
	LL res = 0;
	for(int i = 1; i <= n; ++i)
		res += phi[i];
	return res;
}

参考:
874. 筛法求欧拉函数



快速幂

  1. 快速幂——反复平方法:时间复杂度:算法课_第四章_数学知识_#include_09
//反复平方法
int qmi(int a, int b, int p)
{
int res = 1;
while(b)
{
	if(b & 1) //检查个位
		res = (LL)res*a%p;
	a = (LL)a*a%p;
	b >>= 1;  //把b的个位删掉
}
return res;
}
  1. 快速幂求逆元——欧拉定理、费马小定理


#include <iostream>
 using namespace std;
 
 typedef long long LL;
 int n;
 int a, b, p;
 //反复平方法
 int qmi(int a, int b, int p)
 {
 	int res = 1;
 	while(b)
 	{
 		if(b & 1) //检查个位
 			res = (LL)res*a%p;
 		a = (LL)a*a%p;
 		b >>= 1;  //把b的个位删掉
 	}
 	return res;
 }
 
 int main()
 {
 	cin>>n;
 	while(n--)
 	{
 		cin>>a>>p;
 		int res = qmi(a, p-2, p);
 		if(a%p) cout<<res<<endl;
 		else cout<<"impossible"<<endl;
 	}
 	return 0;
 }
 ```

欧几里得

  1. 扩展欧几里得算法——裴蜀定理


int exgcd(int a, int b, int &x, int &y)  //要求
{
    if(!b)
    {
    	x = 1, y = 0;
    	return a;
    }
    int d = exgcd(b, a%b, x, y);  //已经求好
    int z = x; x = y; y = z-y*(a/b); //得出参数的x、y
    return d;
}
  1. 线性同余方程——扩展欧几里得算法


typedef long long LL;
int exgcd(int a, int b, int &x, int &y)  //要求
{
  if(!b)
{
  x = 1, y = 0;
  return a;
}
  int d = exgcd(b, a%b, x, y);  //已经求好
  int z = x; x = y; y = z-y*(a/b); //得出参数的x、y
  return d;
}
int main()
{
  int n;
  scanf("%d", &n);
  while(n--)
  {
  int a, b, m, x, y;
  scanf("%d%d%d", &a, &b, &m);
  int d = exgcd(a, m, x, y);
  if(b%d) printf("impossible\n");
  else
  	printf("%d\n", (LL)x*(b/d)%m);
  	//  不可以写成x/d*b, 因为上面保证d能整除b,但不能保证d能整除x
  }
  return 0;
}


博弈论入门

  1. Nim游戏
#include <iostream>
using namespace std;
    
int n;
int ans;
int main()
{
cin>>n;
int x;
while(n--)
{
cin>>x;
ans ^= x;
}
if(ans)
cout<<"Yes"<<endl;
else 
cout<<"No"<<endl;
return 0;
}
  1. 集合——Nim游戏


#include <iostream>
#include <cstring>
#include <unordered_map>
#include <algorithm>
using namespace std;
/*
mex(S):不属于该集合S的最小非负整数
SG(终点) = 0
SG(X)从后往前求出来的
n个图(石子)的起点的SG值异或起来!=0 先手必胜
*/
const int N = 110, M = 10010;
int n, m;
int s[N], f[M];
int sg(int x)  //记忆化搜索
{
	if(f[x] != -1) return f[x];
	unordered_set<int> S;
	for(int i = 0; i < m; ++i)
	{
		int sum = s[i];
		if(x >= sum) S.insert(sg(x-sum));
	}
	for(int i = 0; ; i++)
		if(!S.count(i))
		return f[x] = i;
	
	
}


int main()
{
	cin>>m;
	for(int i = 0; i < m; ++i) cin>>s[i];
	cin>>n;
	memset(f, -1, sizeof(f));
	for(int i = 0; i < n; ++i)
	{
		int x;
		cin>>x;
		res ^= sg(x);
	}
	if(res) 
		puts("Yes");
	else 
		puts("No");
	
	return 0;
}