欢迎来到我的频道[【点击跳转专栏】]
作者说:我想说 基础 不等于 简单 ;算法能力不是一蹴而就的,而是来自日积月累的积累和练习!积小流终成江海,诸君 加油!!
文章目录
- [1. 模拟算法](#1. 模拟算法)
- [1.1 多项式输出](#1.1 多项式输出)
- [1.2 蛇形⽅阵](#1.2 蛇形⽅阵)
- [1.3 字符串的展开](#1.3 字符串的展开)
- [2. 高精度算法](#2. 高精度算法)
- [2.1 高精度加法](#2.1 高精度加法)
- [2.2 高精度减法](#2.2 高精度减法)
- [2.3 高精度乘法](#2.3 高精度乘法)
- [2.4 高精度除法(高精度/低精度)](#2.4 高精度除法(高精度/低精度))
1. 模拟算法
模拟,顾名思义,就是题目让你做什么你就做什么,考察的是将思路转化成代码的代码能力。
这类题一般较为简单,属于竞赛里面的签到题(但是,万事无绝对,也有可能会出现让人非常难受的模拟题),我们在学习语法阶段接触的题,大多数都属于模拟题。
1.1 多项式输出
https://www.luogu.com.cn/problem/P1067

【解法】 模拟+分类讨论,对于一元 n 次方程的的最终结果,我们仅需按照顺序,考虑每一项的三件事情:符号+系数+次数。
- 处理「符号」:
- 如果系数小于 0,直接输出 "-";
- 如果系数大于 0,除了首项不输出 "+",其余全部输出 "+"。
- 处理「系数」:
- 先取一个绝对值,因为正负的问题已经处理过了;
- 当系数不等于 1,直接输出这个数;
- 但是当系数为 1,且是最后一项的时候,这个 1 也是需要输出的;其余情况下的 1 不需要输出。
- 处理「次数」:
- 次数大于 1,输出 "x^" + 对应的次数;
- 次数等于 1,输出 "x";
- 次数小于 1,什么也不输出。
cpp
#include<bits/stdc++.h>
using namespace std;
int n;
int main()
{
cin>>n;
for(int i=n;i>=0;i--)
{
int x;cin>>x;
if(x==0) continue;
//1.符号
if(x<0)
{
cout<<"-";
}
else if(x>0)
{
if(i!=n) cout<<"+";
}
//2.数字
int t=abs(x);
//只要次数大于0次 除1以外都要输出
if(i>0&&t!=1)
{
cout<<t;
}
//次数只要为0次任何数字都要输出
else if(i==0)
{
cout<<t;
}
//3.符号
//只要是非1次方和0次方就输出x^i
if(i>0&&i!=1)
{
cout<<"x^"<<i;
}
//如果是1次方单独输出x
else if(i==1)
{
cout<<"x";
}
}
}
⚠️:这道题没别的 就是慢慢的仔仔细细的分析 不要急!
1.2 蛇形⽅阵
https://www.luogu.com.cn/problem/P5731

模拟填数过程即可,实现方式有很多种,这里给大家介绍一个通用的解法
- 定义方向向量
这道题 我们定义两个数组
int dx[] 和 int dy[]然后在数组 比如说我想往右走 之间dx=[0....]、dy=[1...然后(2,3)-> (2,3)+(0,1)即可!具体看图!
- 根据规则结合方向向量填数
- 朝一个方向走,一边走一边填数,直到越界
- 越界之后,结合方向向量,重新计算出新的坐标以及方向
在大多数情况下,其实
矩阵横纵空一行(类似数组0下标不用)这会帮我们对于边界情况处理有很大帮助!当然事情无绝对!大部分情况是这样!我们用图片模拟下下4的情况!
我们一直往右走 走到
(1,5)发现越界!然后我们启用下这个方向
然后改走到
(2,4)后面就是重复这个过程了!⚠️:
- 这里的
越界有两种情况 第一种是过界,而第二种则是下一个位置 里面已经填过元素了(arr[tx][ty]!=0)了!- 当走完一圈怎么回来?很简单
pos=(pos+1)%4即可!(3+1)%4=0
边界:
对齐规则:
| 格式符 | 对齐方式 | 含义 | 示例 (数字为 5) |
|---|---|---|---|
%3d |
右对齐 (默认) | 占用 3 个字符宽度,不足补空格 | " 5" (前面两个空格) |
%-3d |
左对齐 | 占用 3 个字符宽度,不足补空格 | "5 " (后面两个空格) |
%03d |
右对齐 (补零) | 占用 3 个字符宽度,不足补 0 | "005" (前面两个零) |
代码:
cpp
#include<bits/stdc++.h>
using namespace std;
const int N=20;
//定义右、下、左、上4个方向
int dx[]={0,1,0,-1};
int dy[]={1,0,-1,0};
int arr[N][N];
int main()
{
int n;cin>>n;
int pos=0;//方向 数组的坐标!0:此时是右走!
//初始位置
int x=1,y=1;
for(int i=1;i<=n*n;i++)
{
arr[x][y]=i;
//定义一个暂时的tx和ty判断下一步会不会越界
int tx=x+dx[pos];
int ty=y+dy[pos];
//边界详细解答请看上图!
//同时还要检查下一步位置是否被占用
if(tx<1||tx>n||ty<1||ty>n||arr[tx][ty]!=0)
{
pos=(pos+1)%4;//换方向
tx=x+dx[pos],ty=y+dy[pos];
}
x=tx,y=ty;
}
//遍历数组
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
printf("%3d",arr[i][j]);
}
puts("");
}
}
1.3 字符串的展开
https://www.luogu.com.cn/problem/P1098
- 整体策略:模拟
这道题的核心是模拟。你需要严格按照题目给出的规则,像一个精密的机器人一样,逐个处理输入字符串中的每个字符,而不是试图寻找一个复杂的数学公式。 - 核心操作:遍历与分类
- 遍历 :使用一个
for循环,从头到尾扫描输入的字符串s。 - 分类讨论 :对于扫描到的每一个字符
s[i],根据它的具体情况进行判断和处理。
- 遍历 :使用一个
- 关键判断:识别"连字符"
- 遍历时,最重要的判断是:当前字符
s[i]是否是一个需要被"展开"的连字符-? - 显然,如果
s[i]不是-,或者这个-在字符串的最开始(i == 0)或最末尾(i == n-1),那么它肯定不是一个可以展开的连字符。这种情况下,直接将s[i]添加到结果字符串中即可。
- 遍历时,最重要的判断是:当前字符
- 展开条件判断
- 如果
s[i]是-,并且它在字符串中间,那么就要检查它是否满足展开的条件。 - 条件是:
s[i-1]和s[i+1]必须是同类型的字符(要么都是数字,要么都是小写字母),并且s[i-1]的ASCII码要小于s[i+1]的ASCII码。 - 如果满足,说明
s[i-1]-s[i+1]这个片段需要被展开。
- 如果
- 执行展开(
add函数)- 当确认需要展开时,调用一个专门的
add函数来处理s[i-1]和s[i+1]之间的字符。 add函数内部会:- 遍历
s[i-1]+1到s[i+1]-1之间的所有字符。 - 根据参数
p1对每个字符进行样式变换(不变、转大写、变星号)。 - 根据参数
p2对变换后的字符进行重复。 - 根据参数
p3决定最终输出的顺序(正序或逆序)。 - 将处理好的结果拼接到最终的答案字符串后面。
- 遍历
- 当确认需要展开时,调用一个专门的
- 处理不满足展开的连字符
- 如果
s[i]是-,但不满足展开条件(例如s[i-1]是字母,s[i+1]是数字),则这个-本身就是最终结果的一部分,需要原样添加到答案字符串中。
- 如果
通过以上步骤,就能完整地模拟出字符串展开的全过程。
cpp
#include<bits/stdc++.h>
using namespace std;
//碰到这种一个不小心就容易出错的题目,我这里建议是先写好main函数里面的内容
//然后再实现详细的函数 所以这里利用全局变量就显得很有性价比了
int p1,p2,p3,n;
string s,ret;
//⚠️:先看主函数!再看其他函数功能的具体实现!
bool isdig(char ch)
{
return ch>='0'&&ch<='9';
}
bool islet(char ch)
{
return ch>='a'&&ch<='z';
}
// 把 [left, right] 之间的字符展开
// left, right 这两个字符是不做处理
void add(char left,char right)
{
string t;
//遍历中间字符
for(char ch=left+1;ch<right;ch++)
{
char tmp=ch;
if(p1 == 2 && islet(tmp))
{
tmp -= 32; // ⼩写变⼤写
}
else if(p1 == 3)
{
tmp = '*'; // 变成星号
}
// 处理 p2
for(int i = 0; i < p2; i++)
{
t+=tmp;
}
}
// 处理 p3
if(p3 == 2)
reverse(t.begin(), t.end());
ret+=t;
}
int main()
{
cin>>p1>>p2>>p3>>s;
//思考边界情况 遍历字符串s
n=s.size();
for(int i=0;i<n;i++)
{
char ch=s[i];
//1.这些是字符的非法地值
//当'-'再0位置或者n-1位置 无法展开 或者ch不是'-' 号也不需要展开!
if(i==0||i==n-1||ch!='-')
{
ret+=ch;
}
else
{
char left=s[i-1],right=s[i+1];
//判断是否要展开
//isgig 判断是不是数字
//islet 判断是不是字母
if(isdig(left) && isdig(right) && right > left
||
islet(left) && islet(right) && right > left)
{
//展开
add(left, right);
}
//如果不需要展开就维持原样输入进去
else
{
ret += ch;
}
}
}
cout << ret << endl;
}
2. 高精度算法
当数据的值特别大,各种类型都存不下的时候,此时就要用高精度算法来计算加减乘除:
- 先用字符串读入这个数,然后用数组逆序存储该数的每一位;
- 利用数组,模拟加减乘除运算的过程。
高精度算法本质上还是模拟算法,用代码模拟小学列竖式计算加减乘除的过程。
2.1 高精度加法
https://www.luogu.com.cn/problem/P1601

模拟⼩学「列竖式」计算「两数相加」的过程。
- 用字符串读入数据;
- 将字符串的每一位拆分,逆序放在数组中(不过要注意减去字符
'0'因为数组里面要存储数字); - 模拟列竖式计算的过程:
a. 对应位累加;
b. 处理进位;(x/10)
c. 处理余数。(x%10) - 处理结果的位数。(
判断c[lc]是否为0)
模拟过程如下 用三个数组 两个存储相加的数 另外一个存储结果!
cpp
#include<bits/stdc++.h>
using namespace std;
const int N=1e6;
int la,lb,lc;//用于存储三个数组中数据的长度
int a[N],b[N],c[N];
string s1,s2;
//高精度加法的实现
void add(int a[],int b[],int c[])
{
for(int i=0;i<lc;i++)
{
c[i]+=b[i]+a[i];
c[i+1]+=c[i]/10;//是否进位
c[i]=c[i]%10;//保留余数
}
//看看是否lc会不会加一
//eg:9+9=18 总位数最多可能加1位
if(c[lc]) lc++;
}
int main()
{
cin>>s1>>s2;
la=s1.size(),lb=s2.size(),lc=max(la,lb);
//把s1和s2存储到对应数组中
//一定一定要记得减去'0'
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];
}
}
2.2 高精度减法
https://www.luogu.com.cn/problem/P2142

模拟⼩学「列竖式」计算「两数相减」的过程

- 用字符串读入数据;
- 判断两个数的大小,让较大的数在前(
小学减法也只有大减小 是否加负号那是单独需要考虑的事情!)
注意字典序 vs 数的大小:
a. 若位数不等:按照字符串的长度比较。
b. 然后再位数相等:按字典序比较; - 将字符串的每一位拆分,逆序放在数组中;
- 模拟列竖式计算的过程:
a. 对应位求差;(c[i]=a[i]-b[i])
b. 处理借位;(c[i+1]-=1;c[i]+=10) - 处理前导零。(
lc > 1 && c[lc - 1] == 0如果结果为0依然要输出)
cpp
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
string s1,s2;
int a[N],b[N],c[N];
int la,lb,lc;
//比较
//a. 若位数不等:按照字符串的长度比较。
//b. 然后再位数相等:按字典序比较;
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];//正常相减 eg:22-3 2-3=-1
//判断是否需要借位
if(c[i]<0)
{
c[i+1]-=1; //此时 十号位 2-1=1
c[i]+=10; //此时 -1+10=9 借位成功
}
}
//判断是否要处理前导0
//注意要保证 结果为0时候保证还能输出0 所以lc要大于1
while(lc>1&&c[lc-1]==0)
{
lc--;
}
}
int main()
{
cin>>s1>>s2;
//如果s1小于s2为真
if(cmp(s1,s2))
{
cout<<"-";
s1.swap(s2);//保证s1存储大于的数字方便做减法
}
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];
}
}
2.3 高精度乘法
https://www.luogu.com.cn/problem/P1303

无进位相乘再相加:
- 还是「列竖式」,但是每一位相乘的时候不考虑进位,直接把乘的结果放在对应位上;
- 等到所有对应位置「乘完」并且「累加完」之后,「统一处理进位」。
如下图所示:
- 用字符串读入数据;
- 将字符串的每一位拆分,逆序放在数组中;
- 模拟无进位相乘再相加的过程:
a. 对应位求乘积;
注意:同时我们发现了一个规律 0下标的数和0下标的数字相乘 放在0下标 1下标和0下标的数字相乘放在1下标 依次类推
b. 乘完之后处理进位;
c. 处理余数;(因为比如数 99*999 最大只能是个五位数 也就是lc的长度一定小于等于la+lc) - 处理前导零。
cpp
#include<bits/stdc++.h>
using namespace std;
const int N=1e6;
string s1,s2;
int la,lb,lc;
int a[N],b[N],c[N];
//高精度乘法的实现
void mul(int a[],int b[],int c[])
{
//我们通过两个for循环来模拟⽆进位相乘再相加
for(int i=0;i<la;i++)
{
for(int j=0;j<lb;j++)
{
c[j+i]+=a[i]*b[j];
}
}
//处理进位
for(int i=0;i<lc;i++)
{
c[i+1]+=c[i]/10;
c[i]%=10;
}
// 处理前导零 结果可能为0 所以0依然要输出
while(lc>1&&c[lc-1]==0)
{
lc--;
}
}
int main()
{
cin>>s1>>s2;
la=s1.size(),lb=s2.size(),lc=la+lb;
// 1. 拆分每⼀位,逆序放在数组中
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';
}
//2.高精度乘法
mul(a,b,c);
//3.输出结果
for(int i=lc-1;i>=0;i--)
{
cout<<c[i];
}
}
2.4 高精度除法(高精度/低精度)
https://www.luogu.com.cn/problem/P1480

模拟⼩学「列竖式」计算「两数相除」的过程(注意,我们这⾥是「⾼精度 ÷ 低精度」)。
高精度除法核心思路:
- 双指针/变量定义 :定义一个指针
i从「高位」遍历被除数数组,一个变量t标记当前「被除的数」,记除数是b。 - 更新被除数 :更新当前被除的数
t = t * 10 + a[i]。 - 计算商与余数 :
t / b表示这一位的商,t % b表示这一位的余数。 - 更新余数 :用
t记录本次计算后的余数(即t % b),然后遍历到下一位,重复上述过程。 - 最终结果 :被除数遍历完毕后,
t里面存的就是最终的余数。 - 清理前导零 :计算出的商数组可能存在前导
0,需要进行清理。(lc小于等于la)
看不明白是吧 我们模拟一下 1234/45 试试:
| 步骤 | i |
a[i] (当前位) |
t (更新前) |
t = t * 10 + a[i] |
t / 45 (商) |
t % 45 (余数) |
t (更新后) |
商数组 result |
|---|---|---|---|---|---|---|---|---|
| 初始化 | - | - | 0 |
- | - | - | - | [] |
| 1 | 0 | 1 |
0 |
0 * 10 + 1 = 1 |
1 / 45 = 0 |
1 % 45 = 1 |
1 |
[0] |
| 2 | 1 | 2 |
1 |
1 * 10 + 2 = 12 |
12 / 45 = 0 |
12 % 45 = 12 |
12 |
[0, 0] |
| 3 | 2 | 3 |
12 |
12 * 10 + 3 = 123 |
123 / 45 = 2 |
123 % 45 = 33 |
33 |
[0, 0, 2] |
| 4 | 3 | 4 |
33 |
33 * 10 + 4 = 334 |
334 / 45 = 7 |
334 % 45 = 19 |
19 |
[0, 0, 2, 7] |
- 遍历结束 :当我们处理完被除数
1234的最后一位4后,整个过程结束。- 最终余数 :此时,变量
t中存储的值19,就是我们计算出的余数。- 最终商 :我们得到了一个商数组
[0, 0, 2, 7]。- 处理前导零 :正如算法描述中提到的,这个数组包含了前导零。我们需要将其去除。
- 去除前导零后,商数组变为
[2, 7]。- 最终答案 :
- 商 :
27- 余数 :
19
cpp
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int a[N],b,c[N];
int la,lc;
long long t=0;//更新被除数 也是最后的余数 注意被除数在更新过程中一定会大于1e9 所以最好用long long
string s;
//模拟高精度除法
void sub(int a[],int b,int c[])
{
for(int i=la-1;i>=0;i--)
{
//1.更新被除数
t=t*10+a[i];
//2.计算商与余数
c[i]=t/b;
t%=b;
}
//处理前导0
while(lc>1&&c[lc-1]==0)
{
lc--;
}
}
int main()
{
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];
}
}









