算法基础详解(一)模拟算法与高精度算法

欢迎来到我的频道[【点击跳转专栏】]

作者说:我想说 基础 不等于 简单 ;算法能力不是一蹴而就的,而是来自日积月累的积累和练习!积小流终成江海,诸君 加油!!

文章目录

  • [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

模拟填数过程即可,实现方式有很多种,这里给大家介绍一个通用的解法

  1. 定义方向向量

这道题 我们定义两个数组 int dx[] 和 int dy[]然后在数组 比如说我想往右走 之间dx=[0....]、dy=[1...然后(2,3)-> (2,3)+(0,1)即可!具体看图!


  1. 根据规则结合方向向量填数
    • 朝一个方向走,一边走一边填数,直到越界
    • 越界之后,结合方向向量,重新计算出新的坐标以及方向

在大多数情况下,其实矩阵横纵空一行(类似数组0下标不用)这会帮我们对于边界情况处理有很大帮助!当然事情无绝对!大部分情况是这样!我们用图片模拟下下4的情况!

我们一直往右走 走到(1,5)发现越界!然后我们启用这个方向

然后改走到(2,4)后面就是重复这个过程了!

⚠️:

  1. 这里的越界有两种情况 第一种是过界,而第二种则是下一个位置 里面已经填过元素了(arr[tx][ty]!=0)了!
  2. 当走完一圈怎么回来?很简单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

  1. 整体策略:模拟
    这道题的核心是模拟。你需要严格按照题目给出的规则,像一个精密的机器人一样,逐个处理输入字符串中的每个字符,而不是试图寻找一个复杂的数学公式。
  2. 核心操作:遍历与分类
    • 遍历 :使用一个 for 循环,从头到尾扫描输入的字符串 s
    • 分类讨论 :对于扫描到的每一个字符 s[i],根据它的具体情况进行判断和处理。
  3. 关键判断:识别"连字符"
    • 遍历时,最重要的判断是:当前字符 s[i] 是否是一个需要被"展开"的连字符 -
    • 显然,如果 s[i] 不是 -,或者这个 - 在字符串的最开始(i == 0)或最末尾(i == n-1),那么它肯定不是一个可以展开的连字符。这种情况下,直接将 s[i] 添加到结果字符串中即可。
  4. 展开条件判断
    • 如果 s[i]-,并且它在字符串中间,那么就要检查它是否满足展开的条件。
    • 条件是:s[i-1]s[i+1] 必须是同类型的字符(要么都是数字,要么都是小写字母),并且 s[i-1] 的ASCII码要小于 s[i+1] 的ASCII码
    • 如果满足,说明 s[i-1]-s[i+1] 这个片段需要被展开。
  5. 执行展开(add 函数)
    • 当确认需要展开时,调用一个专门的 add 函数来处理 s[i-1]s[i+1] 之间的字符。
    • add 函数内部会:
      1. 遍历 s[i-1]+1s[i+1]-1 之间的所有字符。
      2. 根据参数 p1 对每个字符进行样式变换(不变、转大写、变星号)。
      3. 根据参数 p2 对变换后的字符进行重复。
      4. 根据参数 p3 决定最终输出的顺序(正序或逆序)。
      5. 将处理好的结果拼接到最终的答案字符串后面。
  6. 处理不满足展开的连字符
    • 如果 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

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

  1. 用字符串读入数据;
  2. 将字符串的每一位拆分,逆序放在数组中(不过要注意减去字符'0' 因为数组里面要存储数字);
  3. 模拟列竖式计算的过程:
    a. 对应位累加;
    b. 处理进位;(x/10
    c. 处理余数。(x%10)
  4. 处理结果的位数。(判断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

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

  1. 用字符串读入数据;
  2. 判断两个数的大小,让较大的数在前(小学减法也只有大减小 是否加负号那是单独需要考虑的事情!
    注意字典序 vs 数的大小:
    a. 若位数不等:按照字符串的长度比较。
    b. 然后再位数相等:按字典序比较;
  3. 将字符串的每一位拆分,逆序放在数组中;
  4. 模拟列竖式计算的过程:
    a. 对应位求差;(c[i]=a[i]-b[i]
    b. 处理借位;(c[i+1]-=1;c[i]+=10)
  5. 处理前导零。(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

无进位相乘再相加:

  • 还是「列竖式」,但是每一位相乘的时候不考虑进位,直接把乘的结果放在对应位上;
  • 等到所有对应位置「乘完」并且「累加完」之后,「统一处理进位」。
    如下图所示:
  1. 用字符串读入数据;
  2. 将字符串的每一位拆分,逆序放在数组中;
  3. 模拟无进位相乘再相加的过程:
    a. 对应位求乘积;
    注意:同时我们发现了一个规律 0下标的数和0下标的数字相乘 放在0下标 1下标和0下标的数字相乘放在1下标 依次类推
    b. 乘完之后处理进位;
    c. 处理余数;(因为比如数 99*999 最大只能是个五位数 也就是lc的长度一定小于等于la+lc
  4. 处理前导零。
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]
  1. 遍历结束 :当我们处理完被除数 1234 的最后一位 4 后,整个过程结束。
  2. 最终余数 :此时,变量 t 中存储的值 19,就是我们计算出的余数
  3. 最终商 :我们得到了一个商数组 [0, 0, 2, 7]
  4. 处理前导零 :正如算法描述中提到的,这个数组包含了前导零。我们需要将其去除。
    • 去除前导零后,商数组变为 [2, 7]
  5. 最终答案
    • 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];
    }
}
相关推荐
Promise微笑2 小时前
算法对齐还是实战突围?解构GEO优化中方法论与实践的权重博弈
算法
米粒12 小时前
力扣算法刷题 Day 29
算法·leetcode·职场和发展
wfbcg2 小时前
每日算法练习:LeetCode 125. 验证回文串 ✅
算法·leetcode·职场和发展
We་ct2 小时前
LeetCode 295. 数据流的中位数:双堆解法实战解析
开发语言·前端·数据结构·算法·leetcode·typescript·数据流
Aaron15883 小时前
RFSOC+VU13P/VU9P+GPU通用一体化硬件平台
人工智能·算法·fpga开发·硬件架构·硬件工程·信息与通信·基带工程
c++逐梦人3 小时前
DFS剪枝与优化
算法·深度优先·剪枝
量化炼金 (CodeAlchemy)3 小时前
【交易策略】基于随机森林的市场结构预测:机器学习在量化交易中的实战应用
算法·随机森林·机器学习
coder_Eight3 小时前
LRU 缓存实现详解:双向链表 + 哈希表
前端·算法
重生之我是Java开发战士3 小时前
【动态规划】路径问题:不同路径,珠宝的最高价值,下降路径最小和,最小路径和,地下城游戏
算法·游戏·动态规划