【算法笔记】模拟与高精度加减乘除
前言:模拟一般主要考察我们的代码能力,同时多做模拟题还可以提升我们的代码能力,主要考察的是你的代码实现能力,而高精度加减乘除则是一个模板,用于处理多位数运算的问题
1.模拟
1.1多项式输出
代码示范:
#include <iostream> #include <cmath> using namespace std; int main() { int n; cin >> n; for (int i = n; i >= 0; i--) { int x; cin >> x; if (x == 0) continue; if (x < 0) cout << '-'; else { if (i != n) cout << "+"; } //处理数字 x = abs(x); if (x != 1 || (x == 1 && i == 0)) { cout << x; } //处理次数 if (i == 1) cout << "x"; else if(i == 0) continue; else cout << "x^" << i; } }我们可以将这个问题拆解为三个方面: (1) : 处理符号 (2) : 处理数字 (3) : 处理次数
先来看符号问题,符号为 - 号则正常输出,当项目不为第一项则正常输出 + 号
因为我们已经处理过了符号的问题所以我们可以先对这个数字取个绝对值,这里主要需要处理的是数字为0和1的特殊情况,当数字不为1或者数字为1并且是最后一项则正常输出
接下来处理次数的问题,在当次数为1的时候我们不会输出次数,当为0时则不输出,后面的正常情况我们正常输出即可,但是需要注意格式的问题容易掉坑
1.2蛇形方阵
这道题不难理解,主要是处理转向的问题非常麻烦,所以这里介绍一个改变方向的技巧,这个技巧后面还会用到很多,尤其是在dfs和bfs中就很容易遇到这种场景:
这个蛇的转向主要为上、下、左、右,所以我们可以创建X、Y两个方向的偏移量数组, 设坐标为:
那蛇依次向右、下、左、上移动的坐标偏移量为:
所以可以定义数组为:
int dx[4] = {0, 1, 0, -1}; int dy[4] = {1, 0, -1, 0};我们可以先计算出下一步的蛇的坐标,用这些数组来改变蛇的方向,落实在代码上就是:
#include <iostream> using namespace std; const int N = 15; int dx[4] = {0, 1, 0, -1}; int dy[4] = {1, 0, -1, 0}; int he[N][N]; int n; int main() { cin >> n; int pos = 0; int x = 1, y = 1; int cnt = 1; while (cnt <= n*n) { he[x][y] = cnt++; int a = x + dx[pos], b = y + dy[pos]; if (a < 1 || a > n || b < 1 || b > n || he[a][b])//防止越界和重复 { pos = (pos + 1) % 4; a = x + dx[pos]; b = y + dy[pos]; } x = a; y = b; } for (int i = 1; i <= n; i++) { for (int j = 1; j <= n; j++) { printf("%3d", he[i][j]); } printf("\n"); } return 0; }其中模4可以让蛇往固定四个方向走
1.3字符串的展开
这道题非常考验细节,也是让我坐牢的好久,和第一题一样分类讨论一下就好了:
#include <iostream> #include <algorithm> using namespace std; int p1, p2, p3; string s; string ret; int n; bool isdig(char ch) { return ch >= '0' && ch <= '9'; } bool islet(char ch) { return ch >= 'a' && ch <= 'z'; } void add(char left, char right) { char ch = ++left; string sz; //处理p2的情况 for (char i = ch; i < right; i++) { char tem = i; if (p1 == 2 && islet(tem)) tem -= 32; if (p1 == 3) tem = '*'; for (int j = 0; j < p2; j++) { sz += tem; } } if (p3 == 2) reverse(sz.begin(), sz.end()); ret += sz; } int main() { cin >> p1 >> p2 >> p3 >> s; n = s.size(); for (int i = 0; i < n; i++) { char ch = s[i]; if (ch != '-' || i == 0 || i == n - 1) ret += ch; else { char left = s[i - 1], right = s[i + 1]; if (left < right && (isdig(left) && isdig(right)) || (islet(left) && islet(right) && left < right)) { add(left, right); } else ret += ch; } } cout << ret << endl; return 0; }2.高精度加减乘除
2.1高精度加法
当语言内置的类型无法满足多位运算的需求时就需要使用高精度加减乘除这个模板,主要是把我们小学阶段学的加减乘除模拟一下就可以了。
这里我们是用数组来完成每一位的模拟计算,我们先用字符串来读入数字,但是我们需要把里面的字符(数字)逆序存到数组,比如读入12345,在存入数组就要54321的存,这是因为我们是要用低位开始模拟计算的
#include <iostream> using namespace std; const int N = 1e6 + 10; int a[N], b[N], c[N]; int la, lb, lc; string s1, s2; void add(int a[], int b[], int c[]) { for (int i = 0; i < lc; i++) { c[i] += a[i] + b[i]; c[i + 1] = c[i] / 10;//处理进位 c[i] %= 10; } if (c[lc]) lc++;//处理首位 } int main() { cin >> s1 >> s2; la = s1.size(); lb = s2.size(); lc = max(la, lb); for (int i = 0; i < la; i++) a[la - 1 - i] = s1[i] - '0';//转陈数字,逆序着存 for (int i = 0; i < lb; i++) b[lb - 1 - i] = s2[i] - '0'; add(a, b, c); for (int i = lc - 1; i >= 0; i--) { cout << c[i]; } return 0; }2.2高精度减法
减法同样是模拟先,和加法差不多,只不过从需要进位变成了借位,别忘了处理一个前面的零就可以了
先判断一下结果的正负,让绝对值大的减去绝对值小的方便我们模拟:
#include <iostream> using namespace std; const int N = 1e6 + 10; int a[N], b[N], c[N]; int la, lb, lc; string s1, s2; bool cmp(string& s1, string& s2) { if (s1.size() != s2.size()) return s1.size() < s2.size(); return s1 < s2; } void sub(int a[], int b[], int c[]) { for (int i = 0; i < lc; i++) { c[i] += a[i] - b[i]; if (c[i] < 0)//处理借位的情况 { c[i + 1] -= 1; c[i] += 10; } } while (lc > 1 && c[lc - 1] == 0) lc--;//处理前导零 } int main() { cin >> s1 >> s2; if (cmp(s1, s2)) { swap(s1, s2); cout << '-'; } la = s1.size(); lb = s2.size(); lc = max(la, lb); for (int i = 0; i < la; i++) a[la - 1 - i] = s1[i] - '0'; for (int i = 0; i < lb; i++) b[lb - 1 - i] = s2[i] - '0'; sub(a, b, c); for (int i = lc - 1; i >= 0; i--) cout << c[i]; return 0; }2.3高精度乘法
乘法的进位相比与加法麻烦一点,但我发现了一个规律,假如紫色对应着下标的话,那结果的小标应该为两个乘除的下标相加,所以我们可以先把结果存入数组c中,让后通过一层循环像加法那样处理掉所有的进位:
紫色对应着这个数字的下标
最后处理所有的前导零即可:
#include <iostream> using namespace std; const int N = 1e6 + 10; int a[N], b[N], c[N]; int la, lb, lc; string s1, s2; void mul(int a[], int b[], int c[]) { for (int i = 0; i < la; i++) { for (int j = 0; j < lb; j++) { c[i + j] += a[i] * b[j]; } } //处理进位 for (int i = 0; i < lc; i++) { c[i + 1] += c[i] / 10; c[i] %= 10; } while (lc > 1 && c[lc - 1] == 0) lc--;//处理前导零 } int main() { cin >> s1 >> s2; la = s1.size(); lb = s2.size(); lc = la + lb; for (int i = 0; i < la; i++) a[la - 1 - i] = s1[i] - '0'; for (int i = 0; i < lb; i++) b[lb - 1 - i] = s2[i] - '0'; mul(a, b, c); for (int i = lc - 1; i >= 0; i--) cout << c[i]; return 0; }2.4高精度除法
除法也是比较简单的模拟,但是我们这次从高位向低位处理,因为我们平常就是这么算的吧哈哈
然后余数对下一位来说要乘10就完了:
#include <iostream> using namespace std; const int N = 1e6 + 10; int a[N], b, c[N]; int la, lc; void sub(int a[], int b, int c[]) { long long t = 0; for (int i = la - 1; i >= 0; i--) { t = t * 10 + a[i]; c[i] = t / b; t %= b; } while (lc > 1 && c[lc - 1] == 0) lc--; } int main() { string s; cin >> s >> b; la = s.size(); lc = la; for (int i = 0; i < la; i++) a[la - 1 - i] = s[i] - '0'; sub(a, b, c); for (int i = lc - 1; i >= 0; i--) cout << c[i]; return 0; }完
