编程技巧思想专练
目的:磨炼编码技巧,提升编码能力。
文章目录
problem 1
偶斐波那契数
斐波那契数列中的每一项都是前两项的和。由1和2开始生成的斐波那契数列的前10项为:
1,2,3,5,8,13,21,34,55,89,…
考虑该斐波那契数列中不超过四百万的项,求其中为偶数的项之和。
代码演示:
int main(){
int a = 1, b = 2, res = 0;
while(b < 4000000){
if(!(b % 2)) res += b;
b += a;
a = b - a;
}
printf("%d\n",res);
}
答案:4613732
problem 2
最大回文数
回文数就是从前往后读和从后往前读都一样的数。由两个2位数相乘得到的最大的回文数是 9009=91×99。
求由两个3位数相乘得到的最大的回文数。
代码演示:
#include<stdio.h>
int func (int x){
int raw = x, t = 0;
while(raw){
t = t * 10 + raw % 10;
raw /= 10;
}
return x == t;
}
int main(){
int ans = 0;
for (int i = 100; i <= 999; i++){
for (int j = i; j < 1000; j++){
if (!func(i * j)) continue;
(i * j > ans) && (ans = i * j);//第一个条件满足则执行第二个条件
}
}
printf("%d\n",ans);
return 0;
}
答案:906609
problem 3(其他进制回文数判定)
双进制回文数
十进制数585 = 10010010012(二进制表示),因此它在这两种进制下都是回文数。
找出所有小于一百万,且在十进制和二进制下均回文的数,并求它们的和。
(请注意,无论在哪种进制下,回文数均不考虑前导零。)
代码演示:
#include<stdio.h>
int fun(int x, int n){
int raw = x, t = 0;
while(x){
t = t * n + x % n;
x /= n;
}
return raw == t;
}
int main(){
int ans = 0;
for (int i = 1; i < 1000000; i++){
if (fun(i, 10) && fun(i, 2)){
ans += i;
}
}
printf("%d\n", ans);
return 0;
}
答案:872187
problem 4(滑动窗口法)
在如下的1000位数中,连续四个数字的最大乘积是 9×9×8×9=5832。
7316717653133062491922511967442657474235534919493496983520312774506326239578318016984801869478851843858615607891129494954595017379583319528532088055111254069874715852386305071569329096329522744304355766896648950445244523161731856403098711121722383113622298934233803081353362766142828064444866452387493035890729629049156044077239071381051585930796086670172427121883998797908792274921901699720888093776657273330010533678812202354218097512545405947522435258490771167055601360483958644670632441572215539753697817977846174064955149290862569321978468622482839722413756570560574902614079729686524145351004748216637048440319989000889524345065854122758866688116427171479924442928230863465674813919123162824586178664583591245665294765456828489128831426076900422421902267105562632111110937054421750694165896040807198403850962455444362981230987879927244284909188845801561660979191338754992005240636899125607176060588611646710940507754100225698315520005593572972571636269561882670428252483600823257530420752963450
73167176531330624919225119674426574742355349194934 96983520312774506326239578318016984801869478851843 85861560789112949495459501737958331952853208805511 12540698747158523863050715693290963295227443043557 66896648950445244523161731856403098711121722383113 62229893423380308135336276614282806444486645238749 30358907296290491560440772390713810515859307960866 70172427121883998797908792274921901699720888093776 65727333001053367881220235421809751254540594752243 52584907711670556013604839586446706324415722155397 53697817977846174064955149290862569321978468622482 83972241375657056057490261407972968652414535100474 82166370484403199890008895243450658541227588666881 16427171479924442928230863465674813919123162824586 17866458359124566529476545682848912883142607690042 24219022671055626321111109370544217506941658960408 07198403850962455444362981230987879927244284909188 84580156166097919133875499200524063689912560717606 05886116467109405077541002256983155200055935729725 71636269561882670428252483600823257530420752963450
7316717653133062491922511967442657474235534919493496983520312774506326239578318016984801869478851843858615607891129494954595017379583319528532088055111254069874715852386305071569329096329522744304355766896648950445244523161731856403098711121722383113622298934233803081353362766142828064444866452387493035890729629049156044077239071381051585930796086670172427121883998797908792274921901699720888093776657273330010533678812202354218097512545405947522435258490771167055601360483958644670632441572215539753697817977846174064955149290862569321978468622482839722413756570560574902614079729686524145351004748216637048440319989000889524345065854122758866688116427171479924442928230863465674813919123162824586178664583591245665294765456828489128831426076900422421902267105562632111110937054421750694165896040807198403850962455444362981230987879927244284909188845801561660979191338754992005240636899125607176060588611646710940507754100225698315520005593572972571636269561882670428252483600823257530420752963450
求这个位数中连续十三个数字的最大乘积。
本题有一种做题思想:
滑动窗口
1.定长滑动
窗口中含有0.给计数器加1,窗口移除0,计数器减一,计数器不为0时,更新时用0比较
2.不定长滑动(双指针法)
留存一下,还没学到
本题代码演示:
#include<stdio.h>
char str[1005];
long long ans, now, zero_cnt;
long long max(int a,int b){
return a > b ? a : b;
}
int main(){
now = 1;
scanf("%s", str);
for (int i = 0; i <= 999 ; i++){
if (i < 13){
now *= str[i] - '0';
ans = now;
}else{
if (str[i] == '0') zero_cnt++;
else now *= str[i] - '0';
if (str[i - 13] == '0') zero_cnt--;
else now /= str[i - 13] - '0';
if (!zero_cnt){
ans = max(ans, now);
}
}
}
printf("%lld\n",ans);
}
答案 :2091059712
problem 5(方向数组)
方阵最大乘积
在如下的20×20方阵中,有四个呈对角线排列的数被标记为红色。
在这个20×20方阵中,四个呈一直线(竖直、水平或对角线)相邻的数的乘积最大是多少?
数据如下:
08 02 22 97 38 15 00 40 00 75 04 05 07 78 52 12 50 77 91 08
49 49 99 40 17 81 18 57 60 87 17 40 98 43 69 48 04 56 62 00
81 49 31 73 55 79 14 29 93 71 40 67 53 88 30 03 49 13 36 65
52 70 95 23 04 60 11 42 69 24 68 56 01 32 56 71 37 02 36 91
22 31 16 71 51 67 63 89 41 92 36 54 22 40 40 28 66 33 13 80
24 47 32 60 99 03 45 02 44 75 33 53 78 36 84 20 35 17 12 50
32 98 81 28 64 23 67 10 26 38 40 67 59 54 70 66 18 38 64 70
67 26 20 68 02 62 12 20 95 63 94 39 63 08 40 91 66 49 94 21
24 55 58 05 66 73 99 26 97 17 78 78 96 83 14 88 34 89 63 72
21 36 23 09 75 00 76 44 20 45 35 14 00 61 33 97 34 31 33 95
78 17 53 28 22 75 31 67 15 94 03 80 04 62 16 14 09 53 56 92
16 39 05 42 96 35 31 47 55 58 88 24 00 17 54 24 36 29 85 57
86 56 00 48 35 71 89 07 05 44 44 37 44 60 21 58 51 54 17 58
19 80 81 68 05 94 47 69 28 73 92 13 86 52 17 77 04 89 55 40
04 52 08 83 97 35 99 16 07 97 57 32 16 26 26 79 33 27 98 66
88 36 68 87 57 62 20 72 03 46 33 67 46 55 12 32 63 93 53 69
04 42 16 73 38 25 39 11 24 94 72 18 08 46 29 32 40 62 76 36
20 69 36 41 72 30 23 88 34 62 99 69 82 67 59 85 74 04 36 16
20 73 35 29 78 31 90 01 74 31 49 71 48 86 81 16 23 57 05 54
01 70 54 71 83 51 54 69 16 92 33 48 61 43 52 01 89 19 67 48
代码演示:
#include<stdio.h>
int max (int a, int b){
return a > b ? a : b;
}
int num[30][30], ans;
int dirx[4] = {-1, -1, 0, 1};//方向数组
int diry[4] = {0,1,1,1};//方向数组
int main(){
for (int i = 5; i < 25; i++){
for (int j = 5; j < 25; j++){
scanf("%d",&num[i][j]);
}
}
for (int i = 5; i < 25; i++){
for (int j = 5; j < 25; j++){
for (int k = 0; k < 4; k++){
int t = num[i][j];
for (int l = 1; l < 4; l++){
int x = i + dirx[k] * l;
int y = j + diry[k] * l;
t *= num[x][y];
}
ans = max(ans,t);
}
}
}
printf("%d\n",ans);
return 0;
}
答案:70600674
problem 6(高精度数)
求以下一百个50位数之和的前十位数字。
代码实现:
#include<stdio.h>
#include<string.h>
int max(int a, int b){
return a > b ? a : b;
}
char str1[1005], str2[1005];
int num1[1005], num2[1005], ans[1005];
int main(){
scanf("%s", str1);
num1[0] = strlen(str1);
ans[0] = strlen(str1);
for (int i = 0, j = num1[0]; i < num1[0]; i++, j--){
num1[j] = str1[i] - '0';
ans[j] = num1[j];
}
while(~scanf("%s", str2)){
num2[0] = strlen(str2);
for (int i = 0, j = num2[0]; i < num2[0]; i++ ,j--){
num2[j] = str2[i] - '0';
}
ans[0] = max(ans[0], num2[0]);
for (int i = 1; i <= ans[0]; i++){
ans[i] = ans[i] + num2[i];//竖式中每个元素的相加
}
for (int i = 1; i <= ans[0]; i++){
if (ans[i] > 9){//两位数了
ans[i+1] += ans[i] / 10;
ans[i] %= 10;
if (i == ans[0]){
ans[0]++;
}
}
}
for (int i = ans[0]; i > 0; i--){//为了方便检查把这部分移动上来了
printf("%d", ans[i]);
}
printf("\n");
for (int i = 0; i < num2[0]; i++){
num2[i] = 0;
}
}
return 0;
}
乘法
代码演示:
#include<stdio.h>
#include<string.h>
char str1[1005], str2[1005];
int num1[1005], num2[1005], ans[3005];
int main(){
scanf("%s%s",str1, str2);
//printf("%s\n",str1);
//printf("%s\n",str2);
num1[0] = strlen(str1);
num2[0] = strlen(str2);
for (int i = 0, j = num1[0]; i < num1[0]; i++, j--){
num1[j] = str1[i] - '0';
}
for (int i = 0, j = num2[0]; i < num2[0]; i++, j--){
num2[j] = str2[i] - '0';
}
ans[0] = num1[0] + num2[0] - 1;
for (int i = 1; i <= num1[0]; i++){
for (int j = 1; j <= num2[0]; j++){
ans[i + j - 1] += (num1[i] * num2[j]);
//if (ans[i + j - 1] < 0){
// printf("%d %d\n", i, j);
//}
}
}
for (int i = 1; i <= ans[0]; i++){
if (ans[i] > 9){
ans[i + 1] += ans[i] / 10;
ans[i] %= 10;
if (i == ans[0]){
ans[0]++;
}
}
}
//printf("?");
for (int i = ans[0]; i > 0; i--){
printf("%d", ans[i]);
}
printf("\n");
return 0;
}
problem 7(双指针)
1000位斐波那契数
斐波那契数列中第一个包含三位数字的是第12项。F12 = 144。
在斐波那契数列中,第一个包含1000位数字的是第几项?
这道题很值得说道说道:斐波那契数列核心思想就是前一个数与后一个数的和为第三个数。
那么我们可以定义两个指针,a, b。
我们让b指向第一个数1,让a指向第二个数1,然后把后一个数加到前一个数上面,此时b就指向了第三个数2,然后我们交换a,b。
使得b去指向第二个数1,a指向第三个数2。
继续上面操作。将后一个数加到前一个数上面,此时b指向第四个数3,a指向第三个数2,我们交换ab。
使得b去指向第三个数2,a指向第四个数3。
如此循环即可。
代码附上:
#include<stdio.h>
int add(int *n1, int *n2){
n2[0] = n1[0];//因为num[b]在加法运算前始终小于num[a],故新的数字长度一定是n1[0]或n1[0]++.
for (int i = 1; i <= n2[0]; i++){
n2[i] += n1[i];
if (n2[i] > 9){
n2[i+1] += n2[i] / 10;
n2[i] %= 10;
if (i == n2[0]){
n2[0]++;
}
}
}
}
int main(){
int num[2][1005] = {{1, 1}, {1, 1}}, a = 1, b = 0;//让a指向后一个数字,b指向前一个数字
for (int i = 3; 1; i++){
add(num[a], num[b]);
if (num[b][0] >= 1000){
printf("%d\n", i);
break;
}
int temp;
temp = a;
a = b;
b =temp;
}
return 0;
}
problem 8(基础动态规划)
路径数量
从一个2×2网格的左上角出发,若只允许向右或向下移动,则恰好有6条抵达右下角的路径。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3ocBHP04-1622733362178)(C:\Users\16937\AppData\Roaming\Typora\typora-user-images\image-20210523141633397.png)]
对于20×20网格,这样的路径有多少条?
解题思路1:到最终点只有两个途径,从上一个格子or左边的格子。
即到达(x,y)坐标只需要到达(x-1,y) , (x,y-1)的路径的和即可。
代码演示:
#include<stdio.h>
long long ans[25][25];
int main(){
for (int i = 1; i <= 21; i++){
for (int j = 1; j <= 21; j++){
if (i == 1 && j == 1){
ans[i][j] = 1;
}else{
ans[i][j] = ans[i - 1][j] + ans[i][j - 1];
}
}
}
printf("%lld\n", ans[21][21]);
return 0;
}
解题思路2:
比如在四个格子里,不论如何走,一定要走4步,这其中一定有两步往右走,两步往下走。
即为:
C
4
2
=
6
C^2_4 = 6
C42=6
我们可以推断出,在40个格子里,不论如何走,一定要走40步,这其中一定有20步向右走,20步向下走。
即为:C4020 = 137846528820
#include<stdio.h>
long long num = 1;
int main(){
for (int i = 40, j = 1; i > 20; i--, j++){
num *= i;
num /= j;
}
printf("%lld\n",num);
return 0;
}
problem 9(动态规划 + 升级)
从如下的数字三角形的顶端出发,不断移动到下一行与其相邻的元素,所能得到的最大路径和是23。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zr9xlfuj-1622733362179)(C:\Users\16937\AppData\Roaming\Typora\typora-user-images\image-20210523151755609.png)]
如上图,最大路径和为3+7+4+9=23。
从如下的数字三角形顶端出发到达底部,求所能得到的最大路径和。
75
95 64
17 47 82
18 35 87 10
20 04 82 47 65
19 01 23 75 03 34
88 02 77 73 07 63 67
99 65 04 28 06 16 70 92
41 41 26 56 83 40 80 70 33
41 48 72 33 47 32 37 16 94 29
53 71 44 65 25 43 91 52 97 51 14
70 11 33 28 77 73 17 78 39 68 17 57
91 71 52 38 17 14 91 43 58 50 27 29 48
63 66 04 68 89 53 67 30 73 16 69 87 40 31
04 62 98 27 23 09 70 98 73 93 38 53 60 04 23
到达(x,y)位置时的最大路径和即为(x, y)的数字加上(x-1,y)的最大路径和或者加上(x-1, y-1)的最大路径和,选二者较大的数字。
代码(自上而下遍历):
#include<stdio.h>
int num[20][20], ans[20][20], fin;
int max(int a, int b)
;
int main(){
for (int i = 1; i<= 15; i++){
for (int j = 1; j <= i; j++){
scanf("%d",&num[i][j]);
}
}
for (int i = 1; i <= 15; i++){
for (int j = 1; j <= i; j++){
ans[i][j] = num[i][j] + max(ans[i - 1][j], ans[i - 1][j - 1]);
fin = max(fin, ans[i][j]);
}
}
printf("%d\n",fin);
}
int max(int a, int b){
return a > b ? a : b;
}
代码(自下而上遍历):
#include<stdio.h>
int num[20][20], ans[20][20], fin;
int max(int a, int b)
;
int main(){
for (int i = 1; i<= 15; i++){
for (int j = 1; j <= i; j++){
scanf("%d",&num[i][j]);
}
}
for (int i = 15; i > 0; i--){
for (int j = 1; j <= i; j++){
ans[i][j] = num[i][j] + max(ans[i + 1][j], ans[i + 1][j + 1]);
}
}
printf("%d", ans[1][1]);
return 0;
}
int max(int a, int b){
return a > b ? a : b;
}
为什么举以上两种例子呢?
因为二者同时遍历可以在某点得到最大和,根据这个原理,我们可以求得去掉某点的最大路径和
相信大家都学过树塔问题,题目很简单求最大化一个三角形数塔从上往下走的路径和。走的规则是:(i,j)(i,j) 号点只能走向 (i+1,j)(i+1,j) 或者 (i+1,j+1)(i+1,j+1)。如下图是一个数塔,映射到该数塔上行走的规则为:从左上角的点开始,向下走或向右下走直到最底层结束。
1
3 8
2 5 0
1 4 3 8
1 4 2 5 0
路径最大和是 1+8+5+4+4=221+8+5+4+4=22,1+8+5+3+5=221+8+5+3+5=22 或者 1+8+0+8+5=221+8+0+8+5=22。
小 SS 觉得这个问题 so easyso easy。于是他提高了点难度,他每次 banban 掉一个点(即规定哪个点不能经过),然后询问你不走该点的最大路径和。当然他上一个询问被 banban 掉的点过一个询问会恢复(即每次他在原图的基础上 banban 掉一个点,而不是永久化的修改)。
输入
第一行包括两个正整数 N,MN,M 分别表示数塔的高和询问次数。
以下 NN 行,第 ii 行包括用空格隔开的 i−1i−1 个数,描述一个高为 NN 的数塔。
而后 MM 行,每行包括两个数 X,YX,Y,表示第 XX 行第 YY 列的数塔上的点被小 SS banban 掉,无法通行。
(由于读入数据较大,请使用较为快速的读入方式)
输出
MM 行每行包括一个非负整数,表示在原图的基础上 banban 掉一个点后的最大路径和,如果被 banban 掉后不存在任意一条路径,则输出 −1−1。
样例输入
5 3
1
3 8
2 5 0
1 4 3 8
1 4 2 5 0
2 2
5 4
1 1
样例输出
17
22
-1
说明:
第一次:
1
3 X
2 5 0
1 4 3 8
1 4 2 5 0
1+3+5+4+4 = 17 或者 1+3+5+3+5=17
解题思路:
我们只需要找到遍历过某点得到的最大路径和即可,需要储存每一行的最大值和次大值,以及最大值所在行的列下标,便于查找。
代码演示:
#include<stdio.h>
int max(int a, int b);
int n, m, num[1005][1005], up[1005][1005], dow[1005][1005], sum[1005][1005], ans[1005][3];
int main(){
scanf("%d%d",&n, &m);
for (int i = 1; i <= n; i++){
for (int j = 1; j <= i; j++){
scanf("%d", &num[i][j]);
}
}
for(int i = 1; i <= n; i++){
for (int j = 1; j <= i; j++){
up[i][j] = num[i][j] + max(up[i- 1][j - 1], up[i - 1][j]);
}
}
for (int i = n; i > 0; i--){
for (int j = 1; j <= i; j++){
dow[i][j] = num[i][j] + max(dow[i+1][j],dow[i+1][j+1]);
}
}
for (int i = 1; i <= n; i++){
for (int j = 1; j <= i; j++){
sum[i][j] = up[i][j] + dow[i][j] - num[i][j];
}
}
for (int i = 2; i <= n; i++){
int mmax = 0, mmax2 = 0, ind = 0;
for (int j = 1; j <= i; j++){
if (sum[i][j] > mmax){
mmax2 = mmax;
mmax = sum[i][j];
ind =j;
}else if (sum[i][j] > mmax2){
mmax2 = sum[i][j];
}
}
ans[i][0] = mmax, ans[i][1] = mmax2, ans[i][2] = ind;
}
for (int i = 0; i < m; i++){
int a, b;
scanf("%d%d", &a, &b);
if (a == 1){
printf("-1\n");
}else if (ans[a][2] == b){
printf("%d\n",ans[a][1]);
}else{
printf("%d\n",ans[a][0]);
}
}
return 0;
}
int max(int a, int b){
return a > b ? a : b;
}
problem 10 (前缀和)
前缀和:
给定一数组 num[10] = {1,2,3,4,5,6,7,8,910}
计算前缀和数组:sum[10] = {1,3,6,10,15,21,28,36,45,55}
前缀和的思想就是可以得到当前截止当前位置的所有元素的和。因为可以在第一次遍历时便计算,所以主要目的是减少时间复杂度。
下面看例题:
题目描述
约翰和他的奶牛在大草原漫游,在一块石头上发现了一些有趣的碑文。碑文似乎是一个神秘古老的语言,只包括三个大写字母 C,O,W。尽管约翰看不懂,但是令他高兴的是,C,O,W 的顺序形式构成了一句他最喜欢的奶牛单词 “COW”。现在,他想知道有多少次 COW 出现在文本中。如果 COW 内穿插了其他字符,只要 COW字符出现在正确的顺序,约翰也不介意。甚至,他也不介意出现不同的 COW共享一些字母。例如 COOWWW 算出现了 8 次
输入
第一行一个整数 N。(1≤N≤105)
第二行为含有 N 个字符的字符串,字符只可能是 C,O,WC,O,W。
输出
输出 COW 作为输入字符串的字串出现的次数(不一定是连续的)。
答案可能会很大。
样例输入
6
COOWWW
样例输出
6
思路分析:
本题有两种解法,一种就是纯暴力,利用三重循环,三次遍历字符串数组,得到答案。
另一种较为聪明的就是,由于是三个字母,且字母可通用,所以我只需要找到字母O之后的W字母的数量与字母O之前的C字母就可以得到COW的数量。再把这些COW的数量累加起来,得到答案。
problem 11(特殊数字的反向思考)
优雅数:
题目描述
给定两个数 L,R,求 L,R 之间(含)有多少个“优雅数”。
优雅数的定义:把一个数看做一个长度为 n 的字符串(没有前导零),n 个字符中 n−1 个全相同,有且仅有一个字符不同。例如 33323,119 都是优雅的,99999,2332都是不优雅的。
输入
输入一行两个整数 L,R。(100≤L≤R≤1016)
输出
输出两数间优雅数的个数。
样例输入
110 133
样例输出
13
数据规模与约定
时间限制:1 s
内存限制:256 M
100% 的数据保证 100≤L≤R≤1016
本题思考:
遇到这种特殊数字的问题,难免进行惯性思维,设置一个函数判断数字是否符合要求。而后进行遍历。
但对于本题:其r的范围是1016这就导致了for循环必然会超时。
故我们提出一个新思路。
去构造这个符合要求的特殊数字,然后进行判断它是否在这个区间内,这便大大节省了时间。
这就是一种反向思考。
那么对于本题来说,难点就在于如何构造了。
- 来一个循环,枚举特殊数字。
- 再来一个循环,枚举非特殊数字。如果非特殊数字与特殊数字相同就重来。
- 再来一个循环,枚举给出数字的长度。
- 再来一个循环,枚举给出特殊数字在这串数字中的位置。两个特判分别是,当特殊数字为0时,它不能在第一位上。当非特殊数字是0是,只能让特殊数字在第一位上。
- 最后一个循环,编辑这个特殊数字
综合以上,便完成了整道题:
给出演示代码:
#include<iostream>
using namespace std;
int main(){
long long a, b, ans = 0;
cin >> a >> b;
for (int i = 0; i < 10; i++){//i表示特殊数字
for (int j = 0; j < 10; j++){//j 表示普通数字
if (i == j){
continue;
}
for (int l = 3; l <= 17; l++){//l表示数字的长度
for (int p = 1; p <= l; p++){//p表示特殊数字所在的位置
if (i == 0 && p == 1) continue;
if (j == 0 && p != 1) break;
long long temp = 0;
for (int m = 1; m <= l; m++){//m进行生成数字
if (m == p){
temp = temp * 10 + i;
} else {
temp = temp * 10 + j;
}
}
if (temp >= a && temp <= b){
ans++;
}
}
}
}
}
cout << ans << endl;
return 0;
}