蓝桥杯_数学知识_1 (质数筛法 - 分解质因数 - 约数【约数个数 - 约数之和 - 最大公约数】 )

文章目录

  • 866. 试除法判定质数
  • 868. 筛质数 ( (朴素)埃氏筛法、 线性筛法)
    • 判断素数
    • 埃式筛法 (朴素)
    • 线性筛法
    • 【分解质因数】
  • 869. 试除法求约数 (试除法)
  • 870.约数个数
  • 871. 约数之和
  • 872. 最大公约数

1.数论 【每一步都要想时间复杂度,看能不能做
2.组合计数
3.高斯消元
4.简单博弈论

866. 试除法判定质数

给定 n 个正整数 ai,判定每个数是否是质数。
输入格式
第一行包含整数 n。
接下来 n 行,每行包含一个正整数 ai。
输出格式
共 n 行,其中第 i 行输出第 i 个正整数 ai 是否为质数,是则输出 Yes,否则输出 No。
数据范围
1≤n≤100,
1≤ai≤231-1
输入样例:
2
2
6
输出样例:
Yes
No

质数定义:在大于1的整数中,如果只包含1和本身这两个约数,就被看成质数 , 反之为合数
(1)质数的判定—试除法 O(n1/2) - - - O(sqrt(n))
质数性质:;[d * n / d == n] --> d | n 【'|'表示整除】, 则 n/d | n 枚举成对出现的小的部分的数,即可推全部: d <= n/d – > d2 <= n 【优化原理】

(2)分解质因数—试除法 O(n1/2)
优化:n中最多只包含一个大于 n1/2 ,【证明:质因子相乘不会大于n,反证出最多一个】
筛去质数的倍数
埃及的一个数学家发明的:埃氏筛法
一个质数定理: 1 ~ n中有 n / logn 个质数 n(1 + 1/2 + 1/4 + 1/8 + 1/16 …)调和级数 O(nloglogn) ~= O(n)
线性筛法

  1. i % pj【primes[j] 】== 0 ,primes[j] 一定是i的最小质因子
    2.i % pj != 0 , pj一定小于i的所有质因子,pj也一定是 pj * i 的最小质因子
    对于一个合数x,假设pj是x的最小质因子,当i枚举到x / pj的时候
    每个数只被筛选过一次

868. 筛质数 ( (朴素)埃氏筛法、 线性筛法)

【最佳选用线性筛法】
1、题目:
给定一个正整数n,请你求出1~n中质数的个数。
输入格式
共一行,包含整数n。
输出格式
共一行,包含一个整数,表示1~n中质数的个数。
数据范围
1≤n≤106
输入样例:
8
输出样例:
4

2、引用~基本思想:
(1)朴素筛法:不管是合数还是质数,筛掉2~n中每个数的倍数 O(nlogn);
(2)埃氏筛法:因为每个合数都是由有限个质数相乘得到的,仅筛掉质数的倍数 O(nloglogn);
(3)用线性筛法快(建立质数表)在埃式筛法中,有的合数会被不同的质数筛多次,会浪费时间,
而每个合数的最小质数是唯一的(否则就不是最小质数),所以只需要用每个合数的最小的质数将它筛掉一次 即可O(n)。

判断素数



#include 
using namespace std; 

bool is_prime(int n)
{
	if(n < 2)return false; //先排除非法值 
	for(int i= 2;i <= n / i ; i++) //[防止溢出写法]
		if(n % i == 0)
			return false;
	return true; //如果false会直接返回,所以可以不加else	
	
}

埃式筛法 (朴素)



#include
using namespace std;
const int N = 10000010;
int primes[N] ,cnt;
bool st[N];


bool get_prims(int n)
{
	for(int i = 0;i <= n;i++)
	{
		if(!st[i]) //st质数表 
		{
			primes[cnt ++] = i; 
			for(int j = i + i;j <= n;j += i) st[j] = true;
		}	
	}	
} 

线性筛法

同样n只会被最小质因子筛掉 【提速原理:不会重复筛选质数的倍数】

简记:每找到一个质数 , 筛质数表中存放的质数,必筛到 i 的最小质因子


#include
using namespace std;
const int N = 10000010;
int primes[N] ,cnt;
bool st[N];//true筛去 ,剩下未被标记的为质数

void get_prims(int n)   //【找质数-不是判断质数,建立质数表】
{
	for(int i = 2;i <= n;i++)
	{	//因为i从2开始找质数 2,3,5都是质数,且4是2的倍数,在2判合数时已经被标记,会跳过
		if(!st[i]) primes[cnt++] = i;//primes质数表cnt统计质数个数
		for(int j = 0;primes[j] <= n / i;j ++) //遍历到 n/(自身= i) 即可
		{
			st[primes[j] * i] = true;  //st为true筛去合数 ,剩下false为质数
			if(i % primes[j] == 0)break;//成立时 , primes[j] 一定是i的最小质因子 (不成立时,primes[j]一定小于j的最小质因子,则也包含i的最小质因子)  
		}	
	}
}

/*

【分解质因数】

复杂度介于 O(logn) ~ O(n1/2)

  简记:质因数^cnt^ 的乘积
 	联系约数个数:就是unorder_map primes    存放[ 质因数,个数(幂)]     

void divide(int n) //分解结果一定是质数
{
	for(int i = 2;i <= n / i;i++) 
		if(n % i == 0) //枚举到i,说明 n是i的倍数 && 已经除去了2~~i-1质因子,即n,i也不包含任何2 ~~ i-1之间的质因子,[i就一定是个质数],只能被自己和1整除 
		{
			int s = 0;
			while(n % i == 0)
			{
				n /= i;
				s++;
			}
			printf("%d %d\n",i,s); 		  //存放:primes[cnt++] = i ,sum[cnt++] = s;
		}	
	if(n > 1) printf("%d %d\n",n,1);  //最多可能存在一个 > sqrt(n) 的质因数  [此处优化后,i只要枚举到n/i]
	puts("");
}


869. 试除法求约数 (试除法)

题目描述 :
给定 n 个正整数 ai,对于每个整数 ai,请你按照从小到大的顺序输出它的所有约数。
输入输出格式 :
输入
第一行包含整数 n。
接下来 n 行,每行包含一个整数 ai。
输出
输出共 n 行,其中第 i 行输出第 i 个整数 ai 的所有约数。
输入输出样例 :
输入
2
6
8
输出
1 2 3 6
1 2 4 8

思路:
①枚举 1 ~ sqrt(n) ,判断是否x % i == 0
②因为约数都是成对出现【 d * ( n/d ) == n --> d | n ,n/d | n [‘|’:整除],只枚举到小的那个约数就可以】
若是x % i != i,我们再将x % i也储存到结果数组
③对res进行sort排序algorithm

简记:  开vector< int > res 
		res.push_back(i) : 约数存放 i与 n/i   ( i != n / i)不要重复存放  
		sort(res.begin(),res.end());  
		main:for(auto t : res) printf("%d ",res[t]) ; puts("");    【c++11】 迭代器遍历换成for 新型
		或 for(int t = 0;t < res.size();t++) printf("%d ",res[t]) ; puts("");  

#include
#include
#include
#include
using namespace std;

vector<int> get_divisors(int n)//试除法 
{
	vector<int> res;
	
	for(int i = 1;i <= n / i ;i++)   //枚举到sqrt(n) ,这样写是为了体现成对出现的约数  可以改成i * i <= n 
		if(n % i == 0) //是约数   ,同时 n/i 也是约数
		{
			res.push_back(i); //放入 
			if(i  != n / i) res.push_back(n / i);	 // 平方相等时,i 与 n/i 两个约数一样,不要重复放
		}	
		
	sort(res.begin(),res.end());   
	return res;  //数组首地址
		
} 

int main() {
	
	int n;
	cin >> n;
	
	while(n --)
	{
		int x;
		cin >> x;
		vector<int> res = get_divisors(x);
		
		for(auto  t : res)printf("%d" , t );    
		puts("");  
		
		/*法二 :【vector数组下标循环(非迭代器)】
		for(int t = 0;t < res.size();t++)
		{									
			cout << res[t] << " ";
		}
		puts("");
		*/
	}
	return 0;

}

870.约数个数

给定n个正整数ai,请你输出这些数的乘积的约数个数,答案对109+7取模。
输入格式
第一行包含整数n。
接下来n行,每行包含一个整数ai。
输出格式
输出一个整数,表示所给正整数的乘积的约数个数,答案需对109+7取模。
数据范围
1≤n≤100,
1≤ai≤2*109
3
2
6
8
输出样例:
12

解题思路:约数个数等于各个质因子的幂指数+1 的乘积,然后利用这个定理,先把各个数分解质因子,用hash表存储幂指数,最后乘起来即可。
分解定理:每种数的分解选法对应一种约数个数
1 ~ n的约数个数共有nlogn , 平均每个数logn个
(所有指数+1 )再相乘 = 约数个数

简记: 
	 	联系约数个数:就是unorder_map primes    对应关系 [ 质因数下标key, 个数(幂)]     
		【unorder_map 存储 {key, mapped} 对的哈希表  】

#include
using namespace std;
#include
#include

const int mod = 1e9 + 7;
typedef long long ll;

int main()
{
	int n;
	cin >> n;
	
	unordered_map <int,int>  primes;
	
	while(n --)
	{
		int x;
		cin >> x;  //累加每个a[i]的质因子个数 
	
		for(int i = 2;i <= x / i;i++)	
		while(x % i == 0)
			{
			x /= i;
			primes[i] ++;//存了所以质因数的指数 
			}
		
		if(x > 1) primes[x] ++;
		
	}
	
	
	ll res = 1;  //乘积初始值: 1
	for(auto prime : primes) res = res * (prime.second + 1) % mod;  //仅此不同

	cout << res << endl;
	return 0;
}

871. 约数之和

给定 n 个正整数 ai,请你输出这些数的乘积的约数之和,答案对 109+7 取模。
输入格式
第一行包含整数 n。
接下来 n 行,每行包含一个整数 ai。
输出格式
输出一个整数,表示所给正整数的乘积的约数之和,答案需对 109+7 取模。
数据范围
1≤n≤100,
1≤ai≤2×109
输入样例:
3
2
6
8
输出样例:
252

用算术基本定理分解质因数
unordered_map一个用来存P,一个用来存对应的α;
记住约数之和公式:( P10 + P11 + … + P1α1 ) × … × ( Pk0 + Pk1 + … + Pk^α k^ ) 【证明:展开式相等】
乘积的个数 (α1 + 1)(α2 + 2)* … *(αk + k) == ( P10 + P11 + … + P1α1 ) , a1 * a2 * … *an == p1α1 *…*pkαk
其中的每一小项用秦九韶算法来计算:while (a – ) t = (t * p + 1) % mod;

简记:【与约数个数算法思路一样:算出约数,个数次幂】     乘积初始值 res  :1
	 main:unordered_map   primes;    {key,次幂}
	 i <= x / i  :满足约数 :primes[i] ++;   
	 判断是否有大于sqrt(n)的约数 :if(n>1)
	 公式:∑ 约数^(幂+1)^
	 for(int j;primes[j] != 0;j++) res = res * (primes[j].second + 1) % mod; 

#include
using namespace std;
#include
#include

const int mod = 1e9 + 7;
typedef long long ll;

int main()
{
	int n;
	cin >> n;
	
	unordered_map <int,int>  primes;
	
	while(n --)
	{
		int x;
		cin >> x;  //累加每个a[i]的质因子个数 
	
		for(int i = 2;i <= x / i;i++)	
		while(x % i == 0)
			{
			x /= i;
			primes[i] ++;//存了所以质因数的指数 
			}
		
		if(x > 1) primes[x] ++;
		
	}
	
	//约数之和
	ll res = 1;  //乘积初始值: 1
	for(auto prime: primes) 
	{
		int p = prime.first , a = prime.second;
		ll t = 1;
		while(a--) t = (t * p + 1) % mod; //pi^0 + pi^1 + ... + pi^a1
		
		res = res * t % mod; 
	} 
	
	//for(auto prime: primes) res = res * (prime.second + 1) % mod;   约数个数
	
	cout << res << endl;
	return 0;
}

872. 最大公约数

给定n对正整数ai,bi,请你求出每对数的最大公约数。
输入格式
第一行包含整数n。
接下来n行,每行包含一个整数对ai,bi。
输出格式
输出共n行,每行输出一个整数对的最大公约数。
数据范围
1≤n≤105,
1≤ai,bi≤2*109
输入样例:
2
3 6
4 6
输出样例:
3
2

最大公约数:欧几里得算法【辗转相除法】
d | a && d | b ==> d | (a+b) ,d | (ax + by)

最核心 gcd(a,b) == gcd(b,a%b)
证明:a mod b = a - 向下取整[a/b] * b = a - c * b
d | b && d | a + c * b ----- > d | a -----> 最核心成立

库函数 __gcd(a,b) ;两个下杠


int my_gcd(int a,int b)
{
	if(a%b == 0)return b;
	return gcd(b,a%b);
}

int gcd_1(int a,int b)
{
	//我写成if(a%b == 0)return b;即下一步b变为0之前,b已是最大公约数,输出
	if(b == 0) return a;  //a%b == 0也就是b = 0
	
	return gcd(b,a % b);
}

int gcd(int a,int b)
{
	return b ? gcd(b,a%b) : a;
}

int main()
{
	int n;
	scanf("%d",&n);
	while(n --)
	{
		int a,b;
		scanf("%d\n",&a,&b);
		printf("%d\n",gcd(a,b));
	}
	
	return 0;
}

你可能感兴趣的:(算法,蓝桥杯,c++)