入门篇-数学问题-《算法笔记》同步笔记总结与补充

专题要点:

  • 编程实现数学算法:如欧几里得算法(最大公约数gcd)
  • 模拟运算法则:如有理数(分数Fraction)四则运算,大整数(高精度)运算,更要注意细节的处理
  • 取模,除法的思想:取模用于取某一数位的数值,除法用于分解原数(在进制转换和分解质因数运用)
  • 判断素数的方法
    • 根号枚举法—— 能否整除2~sqrt(n)
    • 用素数打表得到的bool isPrime[]数组判断
  • 得到素数表的方法
    • 素数筛打表得到int prime[]
    • 暴力枚举,对范围内的数字做根号枚举法
  • 素数的应用:如分解质因子
  • 扩展欧几里得,组合数建议还是学习完动态规划之后,心平气和的时候再看这部分,但愿之后我能记得回头再看一遍
    • 扩展欧几里得:数学推导可以想明白,但编程实现起来还有些困难,数学证明看得头大,尤其是前者引入了很多新的数学专业名词;
    • 组合数:总体比扩展欧几里得容易理解,中间递推公式编程涉及动态规划的思想

可解问题:

  • 有些数学问题确实不容易思考,需要数学的思想考虑问题如乙组1049(找规律)和甲组1049(分析每一位出现“1”的次数)

素数打表+二次探测再散列
A1116.Come on Lets C 素数 + 散列
连续因数分解

细节注意:

  • gcd(a, b):算法结束后,a中存放的是最大公约数,b为0
  • 几个数据结构体
    • 分数(分子up,分母down)
    • 质因数(质因子x,其个数cnt)
    • 大整数(数字各位数d[],数字长度len)

  • 有理数四则运算要注意的细节比较多
    • 分数的化简:三步化简
      * 分母为负,同乘-1
      * 分子为0,分母置1
      * 约分(gcd)
    • 分数乘法:最好用long long防止溢出
    • 分数除法:特判分母为0的情况
    • 分数输出:注意if,else if,else的先后顺序
      • 关于括号:分子为负,输出左括号
      • 先判断整数(分母为1)输出
      • 其次假分数转换为带分数输出,分子用绝对值
      • 最后真分数输出
      • 关于括号:分子或系数为负,输出右括号

分数化简

Fraction reduce(Fraction a)//三步化简 
{
     
	if(a.up == 0) 
	{
     
		a.down = 1;
	}
	if(a.down < 0)
	{
     
		a.up *= -1;
		a.down *= -1;
	}
	Fraction ans = a;
	ll temp = gcd(a.up, a.down);
	if(abs(temp) > 1)
	{
     
		ans.up /= temp;
		ans.down /= temp;
	}
	return ans;
}

分数输出

void display(Fraction a)
{
     
	ll cof = 0;
	if(a.up < 0)
		printf("(");
	if(a.down == 1)//整数 
	{
     
		printf("%lld", a.up);
	}
	else if(abs(a.up) > abs(a.down))//假分数 
	{
     
		cof = a.up / a.down;
		a.up = abs(a.up) % a.down;
		printf("%lld %lld/%lld", cof, a.up, a.down);
	}
	else//真分数 
	{
     
		printf("%lld/%lld", a.up, a.down);
	}
	if(a.up < 0 || cof < 0)
		printf(")");
}

  • 大整数四则运算
    • 加法,乘法:注意处理最后一次进位的问题
    • 减法,除法:注意处理高位为0的问题,还有除法的余数问题
    • 负数处理
  • 素数
    • sqrt(1.0 * n):参数应为浮点数
    • 使用素数筛注意i,j的范围:均小于maxn,含义是枚举maxn范围内的所有素数
    • 要对0,1做特判:这两个数字均不是素数
    • 打表时,要注意isPrime[]与prime[]数组大小
  • 分解质因数:
    • 数据结构:结构体数组的含义
    • 特判:对质数的分解

个人做题的几点注意:

  • 个人逻辑不够清晰,if,while位置先后顺序不合理,有时候不清楚应该先判断什么,会造成重复和代码冗余
  • 边界细节问题分析不到位,处理不好,数组范围也总出问题
  • 关于素数
    • 区别int prime[]与bool isPrime[]数组:前者只包含素数,后者包含所有的数字,因此如果根据isPrime判断素数的话,需要将isPrime[]开的大一些,以至于大到可以包含范围内所有要枚举的数(包括合数)。例如枚举100内的素数,isPrime[]必须超过100,但prime可以开到30,因为100以内的素数只有25个
  • 素数表与根号枚举优劣
    • 素数表效率高(O(nloglogn)),占用较多的空间,尤其是isPrime[],并且数组大小决定了数据范围的大小;

    • 根号枚举法(开根号sqrt()计算)数据范围更大,效率较低;

    • 根号枚举法(i * i < n),当i达到109会溢出,应该使用long long

    • 两种较易理解的素数筛角度:二者均表示筛掉的为合数,未筛掉的为素数,其区别是数组isPrime,isSift含义和初始值。①isPrime初始默认为素数,筛掉后置为false;②isSift默认为false表示未被筛掉,筛掉后置为true
      素数筛解释:当一重循环为素数时,才会进行二重循环筛选,即二重循环在if判断语句中

int prime[maxn], pNum;//素数表与素数表长度
bool isPrime[maxn];//是否是素数
memset(isPrime, true, sizeof(isPrime));//默认置0
void findPrime()
{
     
	isPrime[0] = isPrime[1] = false;//不是素数
	for(int i = 2; i < maxn; ++i)
	{
     
		if(isPrime[i])//i是素数
		{
     
			prime[pNum++] = i;
			for(int j = i + i; j < maxn; j += i)
			{
     
				isPrime[j] = false;//是合数
			}
		}
	}
}
const int maxn = 100005;
int prime[maxn], pNum;
bool isSift[maxn];//是否被筛掉,true为被筛掉,筛掉的是合数,false是素数
void findPrime()
{
     
	isSift[0] = isSift[1] = true;//特判筛去
	for(int i = 2; i < maxn; ++i)
	{
     
		if(isSift[i] == false)
		{
     
			prime[pNum++] = i;
			for(int j = i + i; j < maxn; j += i)
			{
     
				isSift[j] = true;//筛去
			}
		}
	}
}

你可能感兴趣的:(算法笔记,算法,数据结构,c++)