5.1 简单数学
(1)B1019 & A1069
给定任一个各位数字不完全相同的 4 位正整数,如果我们先把 4 个数字按非递增排序,再按非递减排序,然后用第 1 个数字减第 2 个数字,将得到一个新的数字。一直重复这样做,我们很快会停在有“数字黑洞”之称的 6174,这个神奇的数字也叫 Kaprekar 常数。
例如,我们从6767开始,将得到
7766 - 6677 = 1089 9810 - 0189 = 9621 9621 - 1269 = 8352 8532 - 2358 = 6174 7641 - 1467 = 6174 … …
现给定任意 4 位正整数,请编写程序演示到达黑洞的过程。
输入格式:
输出格式:
如果 N 的 4 位数字全相等,则在一行内输出 N - N = 0000;否则将计算的每一步在一行内输出,直到 6174 作为差出现,输出格式见样例。注意每个数字按 4 位数格式输出。
输入样例 1:
输出样例 1:
7766 - 6677 = 1089
9810 - 0189 = 9621
9621 - 1269 = 8352
8532 - 2358 = 6174
输入样例 2:
输出样例 2:
题解:
(1)输出格式的控制,如:高位补0等,不需要单独写函数,printf里面直接%04d就OK了
**单独涉及函数将int转化为数组,再设计一个函数将数组转换为int数字。因为做减法需要是数,但是排序需要是数组,因此需要int与array的相互转化
(3)sort默认从小到大,第三个参数省略即可。降序排列需要写cmp函数
ps: 对结构体数组排序,调用sort时必须写cmp函数,否则会因为不知道对结构体内哪个元素排序而ERROR
代码:
#include <cstdio>#include <algorithm>using namespace std;int n,num[4];bool cmp (int a,int b){return a>b;}void to_array(int n,int num[]){int k=1000;for(int i=0;i<4;++i){num[i] = n/k;n = n%k;k/=10;}}int to_num (int num[]){int sum=0;sum = num[3] + num[2]*10 + num[1]*100 + num[0]*1000;return sum;}int main(){scanf("%d",&n);int MAX,MIN;while (1){int result;to_array(n,num);sort(num,num+4);MIN = to_num(num);sort(num,num+4, cmp);MAX = to_num(num);n = MAX-MIN;printf("%04d - %04d = %04d\n",MAX,MIN,n);if(n==0||n==6174){break;}}return 0;}
5.2 GCD & LCM
GCD:
定理:设a,b均为正整数,则gcd(a,b) = gcd(b,a%b)
Therefore:
递归式: gcd(a,b) = gcd(b,a%b)
递归边界:gcd(a,0) = a
int gcd (int a,int b){if(b==0){return a;}else return gcd(b,a%b);}
LCM:
定理:正整数a,b的最大公约数为d,则最小公倍数为a*b/d. (通过画韦恩图很好理解)
Example:
题目描述
一组正整数的最小公倍数 (LCM) 是可被集合中的所有数字整除的最小正整数。例如,5、7 和 15 的 LCM 为 105。
输入
输入将包含多个问题实例。输入的第一行将包含一个整数,指示问题实例的数量。每个实例将由一行形式为 m n1 n2 n3 …nm,其中 m 是集合中的整数数,n1 …nm 是整数。所有整数都将为正数,并且位于 32 位整数的范围内。
输出
对于每个问题实例,输出一行,其中包含相应的 LCM。所有结果都将位于 32 位整数的范围内。
样例输入
样例输出
题解:
**简单题,没啥说的,但是注意一下数据类型,long long,scanf和printf时注意一下是%lld**
#include <cstdio>int gcd (long long a,long long b){if(b==0){return a;}else return gcd(b,a%b);}long long lcm (long long a,long long b){return (a*b)/gcd(a,b);}int main(){int n;scanf("%d",&n);for(int i=0;i<n;++i){int m;scanf("%d",&m);long long ans[m];for(int j=0;j<m;++j){scanf("%lld",&ans[j]);}for(int p=0;p<=m-2;++p){ans[p+1] = lcm(ans[p],ans[p+1]);}printf("%lld\n",ans[m-1]);}return 0;}
5.3 分数的四则运算
5.3.1 分数的表示与化简
存储分数主要是储存分子和分母,因此,我们通常用结构体来存储。
struct Fraction{int up, down;};

Fraction reduction (Fraction result){if(result.down < 0){result.up = -result.up;result.down = -result.down;}if(result.up==0){result.down = 1;}else{int d = gcd(abs(result.up), abs(result.down));result.up /= d;result.down /= d;}return result;}
5.3.2 分数的四则运算
<br /><br />**【特判】注意:分数的除法需要注意,当除数为0时,即f2.up为0时,需要输出错误信息(自定义),只有当除数不为0时才能用上面的公式。**
5.3.3 分数的输出

void showResult (Fraction r) // 输出分数r{r = reduction(r);if(r.down == 1){printf("%lld",r.up);}else if(abs(r.up)>r.down){ //假分数//r.down不用abs,因为在分数构建时,我们就判断了当分母为0时,分子分母同时取相反数printf("%d %d/%d",r.up/r.down, abs(r.up)%r.down,r.down);}else{ // 不是上面两种情况就是真分数printf("%d/%d",r.up,r.down);}}
**注意:正常情况下,分数的乘法和除法可能会使分子或者分母超过int范围,因此其实我们一般使用long long数据类型来存储。**
5.4 素数
5.4.1 素数的判断
有约数一定是成对出现,也就是一个>sqrt(n),一个<sqrt(n),因此只需要遍历从2~sqrt(n)即可。
bool isPrime (int n){if (n<=1){return false;}for(int i=2;i<= sqrt(n);++i){if(n%i==0){return false;}}return true;}
如果for循环括号里的的判断条件是 i*i<=n,在数据较小时没问题,但是怕相乘后超过int范围。
5.4.2 素数表
**如果我们用上面的方法来判断一个数是否为素数,那么列举前n个正整数的素数的复杂度就是O(n*sqrt(n)),数据量在10****5****量级以下是没有问题的。但是超过10****5****就不容乐观了。因此,我们通常采用下面的这种素数筛的方法,复杂度降为O(nlognlogn)**<br />
// P163 埃氏筛法#include <cstdio>const int maxn = 100;int prime[maxn+1];bool p[maxn+1]={false};int num = 0;void Find_Prime(){for(int i=2;i<=maxn;++i){if(!p[i]){prime[num++] = i;for(int j=i+i;j<=maxn;j+=i){p[j] = true;}}}}int main(){Find_Prime();for(int i=0;i<num;++i){printf("%d ",prime[i]);}return 0;}
**ps: 第二个for循环的第三个参数是 j+=i ,不能写成 j+i 。 (++j 其实就等价于 j+=1)**
(1) B1013
令 Pi 表示第 i 个素数。现任给两个正整数 M≤N≤104,请输出 PM 到 PN 的所有素数。
输入格式:
输出格式:
输出从 PM 到 PN 的所有素数,每 10 个数字占 1 行,其间以空格分隔,但行末不得有多余空格。
输入样例:
输出样例:
11 13 17 19 23 29 31 37 41 43
47 53 59 61 67 71 73 79 83 89
97 101 103
题解:
注意:
(1)格式控制,行末不能有多余空格!
(2) 建议代码修改:函数里加上if(num>=n) break; 降低复杂度,因为只需要找到前n个素数即可
(3) maxn不能设为10000,因为n的上限是10000,前10000个素数的上限不可能是10000.n是前n个素数,是输入的第二个。
#include <cstdio>const int maxn = 10000000;int Prime[maxn];bool p[maxn+1] = {false};int num=0,cnt=0;void Find_Prime (){for(int i=2;i<=maxn;++i){if(!p[i]){Prime[num++] = i;for(int j=i+i;j<=maxn;j+=i){p[j] = true;}}}}int main(){Find_Prime();int m,n;scanf("%d%d",&m,&n);for(int i=m-1;i<=n-1;++i){if(cnt%10==0 && cnt!=0){printf("\n");}if((cnt-9)%10==0 || i==n-1)printf("%d",Prime[i]);elseprintf("%d ",Prime[i]);++cnt;}return 0;}
5.5 质因子分解
**由于每个质因子都可以不止出现一次,因此不妨定义结构体factor,用来存放质因子及其个数,如下:**
struct factor{ //x为质因子,cnt为其个数int x;int cnt;}fac[10];
**考虑到2×3×5×7×11×13×17×19×23×29就已经超过了int范围,因此对一个int型范围的数来说,fac数组的大小只需要开到10就可以了。**<br />** 如果p是n的质因子,那么给fac数组增加质因子p,并初始化其个数为0.然后只要p还是n的质因子,就让n不断的除以p,每次操作令P的个数++,直到p不再是n的因子为止。(如果p不是n的因子,就直接跳过)。**
if(n%Prime[i]==0){ //如果Prime[i]这个质数是n的因子fac[num].x = Prime[i]; //记录该因子fac[num].cnt = 0;while (n%Prime[i]==0){ //计算出质因子Prime[i]的个数fac[num].cnt++;n /= Prime[i];}num++; // 不同质因数个数++}
**如果在上面步骤结束后n仍然>1,说明n有且仅有1个大于sqrt(n)的质因子(有可能是n本身),这时需要把这个质因子加入fac数组,并令其个数为1.**
(1) A1059
Given any positive integer N, you are supposed to find all of its prime factors, and write them in the format N = p_1_k_1×_p_2_k_2×⋯×_pmkm.
Input Specification:
Each input file contains one test case which gives a positive integer N in the range of long int.
Output Specification:
Factor N in the format N = p_1^_k_1_p_2^_k_2…*_pm^km, where pi‘s are prime factors of N in increasing order, and the exponent ki is the number of pi — hence when there is only one pi, ki is 1 and must NOT be printed out.
Sample Input:
Sample Output:
题解:(代码注释有问号不懂)
(1)不放心可以把maxn开大一些(题目说int 范围内正整数进行质因子分解,因此素数开到10**5**应该可以)
(2)别忘记输入为1的时候的特判
(3)main函数里面别忘了调用Find_Prime
(4)下面的maxn写成n不是不行,但是在之前忽略了主函数的出现顺序,之前是先调用,再scanf,所以n在这里永远是0。倒是输出结果永远是blablabla = blablabla (没分解,其实是因为素数表里啥也没有)
代码:
#include <cstdio>#include <cmath>const int maxn = 100010;bool P[maxn] = {false};int num=0;int Prime[maxn] = {0};int n; //输入bool isPrime (int n){if(n==1){return false;}for(int i=2;i<= sqrt(n);++i){if(n % i == 0){return false;}}return true;}int pNum=0; // pNum是前n个数有几个素数void Find_Prime() // 求素数表{for(int i=1;i<maxn;++i){if(isPrime(i)){Prime[pNum++] = i;}}}struct factor{ // x是质因子,cnt是其个数int x;int cnt;}fac[100];int main(){Find_Prime();scanf("%d",&n);if(n==1){printf("1=1");}else{printf("%d=",n);for(int i=0 ; i<pNum && Prime[i]<=sqrt(n) ; ++i){if(n % Prime[i] == 0){fac[num].x = Prime[i];fac[num].cnt = 0;while (n % Prime[i] == 0){fac[num].cnt++;n = n/Prime[i];}num++;}if(n==1) break; //及时推出,节省时间}if(n!=1){fac[num].x = n; //????????????????fac[num++].cnt = 1;}// 按格式输出结果for(int j=0;j<num;++j){if(j>0){printf("*");}printf("%d",fac[j].x);if(fac[j].cnt>1){printf("^%d",fac[j].cnt);}}}return 0;}
附加知识点:
5.6 大整数运算
顾名思义,大整数运算肯定不是我们平时见到的整数,平时整数的运算直接printf就好了,但是大整数指超过int范围的整数,甚至可能是1000位,那我们如何运算呢? 要想进行运算首先应该研究如何进行存储。
5.6.1 大整数的存储(a[1000]是高位,a[0]是低位)
把整数按字符换读入,然后存到数组中。而为了方便随时获取大整数的长度,一般定义一个int型变量len来记录其长度,并和d数组组合成结构体,一般地,定义完需要初始化,防止忘记,我们通常使用构造函数初始化(函数名和结构体名相同,无返回值):
struct bign{int d[1000];int len;bign(){memset(d,0,sizeof (d));len = 0;}};
**由于char数组读入时,整数的高位会变成数组的低位,整数的低位会变成数组的高位,因此需要让字符串倒着赋给d[ ] 数组:**
bign change (char str[]) // 将整数转换为bign{bign a;a.len = strlen(str); // bign的长度就是字符串的长度for(int i=0;i<a.len;++i){a.d[i] = str[a.len-i-1] - '0'; //逆着赋值}return a;}
**如果需要比较两个bign的大小关系,先比len,len相同就从高位到低位依次比较:**
int compare (bign a, bign b) // 返回1->a大,-1->a小,0->一样大{if(a.len>b.len)return 1;else if(a.len<b.len)return -1;else{for(int i=a.len-1;i>=0;--i){if(a.d[i]>b.d[i])return 1;else if(a.d[i]<b.d[i])return -1;}return 0; // 两数相等}}
5.6.2 大整数的四则运算
5.6.2.1 加法
和普通的加法运算一样,和半加器也类似,看代码吧:
bign add (bign a, bign b){bign c;int carry = 0; //carry是进位for(int i = 0; i < a.len || i<b.len; ++i){int temp = a.d[i] + b.d[i] + carry;c.d[c.len++] = temp%10; // 个位数为该结果carry = temp/10; // 十位数为新的进位}if (carry!=0){ // 退出循环时,a和b都没有数了,此时若进位不为0,则直接赋给最高位c.d[c.len++] = carry;}return c;}
可运行的完整代码如下:
#include <cstdio>#include <cstring>struct bign{int d[1000];int len;bign(){memset(d,0,sizeof (d));len = 0;}};bign change (char str[]){bign a;a.len = strlen(str);for(int i=0;i<a.len;++i){a.d[i] = str[a.len-i-1] - '0';}return a;}bign add (bign a, bign b){bign c;int carry;for(int i = 0; i < a.len || i < b.len; ++i){int temp = a.d[i] + b.d[i] + carry;c.d[c.len++] = temp % 10;carry = temp/10;}if(carry != 0){c.d[c.len++] = carry;}return c;}void print (bign a){for(int i = a.len-1; i >= 0; --i){printf("%d",a.d[i]);}}int main(){bign a,b,c;char aa[1000];char bb[1000];scanf("%s%s",aa,bb);a = change(aa);b = change(bb);c = add(a,b);print(c);return 0;}
注意:为了运算时代码方便,我们将数组中索引较大的元素存储成高位,较小的元素存储成低位(change函数逆序存储)。因此输出时(print函数)应从a.len-1开始 !!
5.6.2.2 减法
和普通的减法运算一样,看代码吧:
bign sub (bign a, bign b) // 我觉得默认a>b,如果你知道谁大,可以调用上面的compare函数{bign c;for (int i=0; i < a.len || i < b.len; ++i){if(a.d[i] < b.d[i]){ // 向索引下一位(高位)的元素借位a.d[i+1]--;a.d[i] += 10}c.d[i] = a.d[i] - b.d[i];}// 接下来,去掉最高位的0,同时至少保留一位最低位while (c.len >= 2 && c.d[c.len-1] == 0){c.len--;}return c;}
**有一点需要注意一下,按位减完之后,可能高位有几个0,因此,输出时去掉高位的0,上面的while循环就是这个作用。但是,不能直接从最高位依次去掉0,因为假如结果就是0,去掉0之后就啥也输出不出来了。**
5.6.2.3 高精度(bign)与低精度(int)的乘法

bign multi (bign a, int b){bign c;int carry = 0; // 要赋初值0,看看循环体就懂了for (int i = 0; i < a.len; ++i){int temp = a.d[i] * b + carry;c.d[c.len++] = temp % 10; // 个位作为该位的结果carry = temp / 10; //高位部分作为新的进位}while (carry != 0){c.d[c.len++] = carry % 10;carry /= 10;}return c;}
注意:和加法不一样,加法最高位的进位最多是一位,而乘法的进位可能不是一位数,因此用while做判断。
5.6.2.4 高精度(bign)与低精度(int)的除法

bign divide (bign a, int b, int &r) //r为余数{bign c;c.len = a.len;for(int i = a.len - 1; i >= 0; --i){r = r * 10 + a.d[i];if(r < b){c.d[i] = 0;r = r % b;}else{c.d[i] = r / b; // 商r = r % b; //获得新的余数}}while (c.len >= 2 && c.d[c.len-1] == 0){c.len--; // 和加法一样,去掉最高位的0,但是至少保留一位最低位}return c;}
注意:
(1) c.len = a.len ,因为根据原理,商和高精度数是一一对应的,只是可能存在高位为0的情况。
(2)因为函数每次只能返回一个数据,而很多题经常要求得到余数,因此把余数写成“引用”的形式传入,或者设为全局变量。
5.7 拓展欧几里得算法 (P176)
5.8 组合数
5.8.1 关于n!的一个问题
我们来思考一下n!中有多少个质因子p。例如:6!= 12345*6,于是6!有四个质因子2,两个质因子3。这个问题最朴素的解法就是计算从1~n的每个数各有多少个质因子p,然后将结果累加,时间复杂度为O(nlogn),代码如下:
int cal (int n, int p) // 计算1~n每个数字含有多少个质因子p, 再相加{int ans = 0;for(int i = 2; i <= n; ++i){int temp = i;while (temp % p == 0){ // 只要temp还是p的倍数ans++;temp /= p;}}return ans;}
**对于这种O(nlogn)的朴素算法,当据数量很大时不可行,因此我们应寻找复杂度更小的算法。下面介绍的算法能惊人地把复杂度降到O(logn)。**<br /> 
int cal (int n, int p) // 计算1~n每个数字含有多少个质因子p, 再相加{int ans = 0;while (n){ans += n / p;n /= p;}return ans;}
**利用这个算法,可以很快地计算出n!的末尾有多少个零:由于末尾0的个数等于n!中因子10的个数,而这有等于n!中质因子5的个数(think about it),因此只需要代入cal(n,5)就可以得到结果。(参数p一定是质数!! 不能直接cal(n,10));**<br /> <br />
int cal (int n, int p){if(n < p)return 0;return n / p + cal(n / p, p);}
