专题要点:
- 编程实现数学算法:如欧几里得算法(最大公约数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;//筛去
}
}
}
}