技术标签: 高精度减法运算 蓝桥杯 大数运算 大数运算 高精度运算 高精度加法运算 洛谷试题题解 高精度乘法运算 算法与数据结构 蓝桥杯试题题解
我们知道,在数学中,数值的大小是没有上限的,但是在计算机中,由于字长的限制,计算机所能表示的范围是有限的,当我们对比较小的数进行运算时,如:1234 + 5678,这样的数值并没有超出计算机的表示范围,所以可以运算。但是当我们在实际的应用中进行大量的数据处理时,会发现参与运算的数往往超过计算机的基本数据类型的表示范围,比如说,在天文学上,如果一个星球距离我们为 100 万光年,那么我们将其化简为公里,或者是米的时候,我们会发现这是一个很大的数。这样计算机将无法对其进行直接计算。
可能我们认为实际应用中的大数也不过就是几百位而已,实际上,在某些领域里,甚至可能出现几百万位的数据进行运算,这是我们很难想象的。如果没有计算机,那么计算效率可想而知。
由于编程语言提供的基本数值数据类型表示的数值范围有限,不能满足较大规模的高精度数值计算,因此需要利用其他方法实现高精度数值的计算,于是产生了大数运算。大数运算主要有加、减、乘三种方法(除法运算可以通过递归或者循环结合着栈来进行求解)。下面将分别予以介绍如何通过算法与数据结构来完成这三类运算。
为便于分析与理解,我们先回顾下多位数加法的流程(如下图所示):
该过程会从数的最低位开始依次往高位进行计算,具体的加数过程为:
所有位均计算完毕,最终得到和为: 124245 124245 124245。
这便是多位数加法的执行过程,这对具有高精度的大数也适用。由于在加法过程中涉及到对各个位的加法运算,因此在处理大数的加法运算时,通常会用一个 int 型数组来存储大数在各个位上的值。例如,数:122333444455555666666,可通过一个足够长的数组 a r y [ ] ary[\ ] ary[ ],使 a r y [ 0 ] = 1 , a r y [ 1 ] = 2 , a r y [ 3 ] = 2 , … , a r y [ 20 ] = 6 ary\left[0\right]=1,ary\left[1\right]=2,ary\left[3\right]=2,\ldots,ary\left[20\right]=6 ary[0]=1,ary[1]=2,ary[3]=2,…,ary[20]=6 进行存储。采取数位与索引大小相对应的存储方式(即数的低位对应较小的索引,高位对应较大的索引),是为了便于大数在执行加法运算时的进位可直接在数组中向后拓展。接下来,就能按照以上思路来扫描数组并对各个位进行加法运算。最后,单独用一层循环处理进位即可。即:
【马蹄集】 MT2192 A+B problem
问题描述
计算 A + B ( 1 ≤ A , B ≤ 10 10000 ) A+B(1\le A,B\le{10}^{10000}) A+B(1≤A,B≤1010000)。
格式
输入格式:两行每行一个整数 A , B A,B A,B。
输出格式:一个整数 A + B A+B A+B。样例
输入:
1
1输出:
2
/*
MT2192 A+B problem
*/
#include<bits/stdc++.h>
using namespace std;
const int N = 1e4+5;
int numa[N], numb[N];
// 计算两个大数之和(输入为字符串)
void getSum(string stra, string strb)
{
// 赋初值
memset(numa, 0, sizeof(numa));
memset(numb, 0, sizeof(numb));
int tmp = 0;
// 将两个字符串保存至 int 型数组中(注意逆序)
for(int i=stra.length()-1; i>=0; i--)
numa[tmp++] = stra[i] - '0';
tmp = 0;
for(int i=strb.length()-1; i>=0; i--)
numb[tmp++] = strb[i] - '0';
// 将数组中的每个数按位进行加法运算
for(int i=0;i<N;i++)
numa[i] += numb[i];
// 对存放加法结果的数组执行进位处理
for(int i=0; i<N; i++){
numa[i+1] += numa[i] / 10;
numa[i] %= 10;
}
}
// 输出大数加法后的结果
void printBigData()
{
// 从最高位向后扫描,直到第 1 个非 0 数字出现
int p = N-1;
while(numa[p] == 0) p--;
while(p >= 0) cout<<numa[p--];
cout<<"\n";
}
int main( )
{
// 获取输入
string stra, strb;
cin>>stra>>strb;
// 对两个大数进行加法运算
getSum(stra, strb);
// 输出和
printBigData();
return 0;
}
为便于分析,我们先回顾下多位数减法的流程(如下图所示):
该过程会从数的最低位开始依次往高位进行计算,具体的减数过程为:
所有位均计算完毕,最终得到差为: 122667 122667 122667。
这便是多位数减法的执行过程,这对具有高精度的大数也适用。同样地,他也需要用到 int 型数组来存储大数在各个位上的值,其存储规则和大数加法一致(即低位对应较小的索引,高位对应较大的索引)。接下来,只需要扫描数组,在每个位上按照以上思路进行减法运算即可得到大数减法的结果。即:
【马蹄集】 MT2193 A-B problem
问题描述
计算 A − B ( 1 ≤ B ≤ A ≤ 10 10000 ) A-B(1\le B\le A\le{10}^{10000}) A−B(1≤B≤A≤1010000)。
格式
输入格式:两行每行一个整数 A , B A,B A,B。
输出格式:一个整数 A − B A-B A−B。样例
输入:
2
1输出:
1
/*
MT2193 A-B problem
*/
#include<bits/stdc++.h>
using namespace std;
const int N = 1e4+5;
int numa[N], numb[N];
// 计算两个大数之差(输入为字符串)
void getSub(string stra, string strb)
{
// 赋初值
memset(numa, 0, sizeof(numa));
memset(numb, 0, sizeof(numb));
int tmp = 0;
// 将两个字符串保存至 int 型数组中(注意逆序)
for(int i=stra.length()-1; i>=0; i--)
numa[tmp++] = stra[i] - '0';
tmp = 0;
for(int i=strb.length()-1; i>=0; i--)
numb[tmp++] = strb[i] - '0';
// 将数组中的每个数按位进行减法运算
for(int i=0;i<N;i++){
numa[i] -= numb[i];
if(numa[i] < 0){
// 借位
numa[i+1]--;
numa[i] += 10;
}
}
}
// 输出大数减法后的结果
void printBigData()
{
// 从最高位向后扫描,直到第 1 个非 0 数字出现
int p = N-1;
while(numa[p] == 0) p--;
while(p > -1) cout<<numa[p--];
cout<<"\n";
}
int main( )
{
// 获取输入
string stra, strb;
cin>>stra>>strb;
// 对两个大数进行减法运算
getSub(stra, strb);
// 输出和
printBigData();
return 0;
}
我们知道,多位数的乘法实际上可分解为某个数与另一个数在各个位上的乘法之和,如下图所示:
从多位数的乘法过程可以看出,它主要分为 4 步:
在实际编码时,我们可用 int 型数组来存储大数在各个位上的值,其存储规则和大数加法一致(即低位对应较小的索引,高位对应较大的索引)。接下来,可通过一个二重循环来对两个大数进行扫描,以实现对两个大数之间的全部数乘运算。这里有一点需要注意:扫描大数的两个索引实际上正指示了当前数乘运算的结果应该存放的位置。即, n u m a [ i ] × n u m b [ j ] = a n s [ i + j ] numa[i]×numb[j]=ans[i+j] numa[i]×numb[j]=ans[i+j]。
【蓝桥杯】 算法提高 P1001 大数运算
问题描述
当两个比较大的整数相乘时,可能会出现数据溢出的情形。为避免溢出,可以采用字符串的方法来实现两个大数之间的乘法。具体来说,首先以字符串的形式输入两个整数,每个整数的长度不会超过15位,然后把它们相乘的结果存储在另一个字符串当中(长度不会超过30位),最后把这个字符串打印出来。例如,假设用户输入为:62773417 和 12345678,则输出结果为:774980393241726。
输入样例
62773417 12345678
输出样例
774980393241726
/*
【蓝桥杯】 算法提高 P1001 大数运算
*/
#include<bits/stdc++.h>
using namespace std;
const int N = 16;
const int NN = N*2;
int arya[N], aryb[N], ans[NN];
// 高精度乘法运算
void getProduct(string stra, string strb)
{
// 初始化存放大数的数组
memset(arya, 0, sizeof(arya));
memset(aryb, 0, sizeof(aryb));
memset(ans, 0, sizeof(ans));
// 将字符串数字保存进整型数组
int tmp = 0;
for(int i=stra.length()-1; i>=0; i--)
arya[tmp++] = stra[i] - '0';
tmp = 0;
for(int i=strb.length()-1; i>=0; i--)
aryb[tmp++] = strb[i] - '0';
// 按位进行乘法
for(int i=0;i<stra.length();i++)
for(int j=0;j<strb.length();j++)
ans[i+j] += arya[i]*aryb[j];
// 进位处理
for(int i=0; i<NN; i++){
ans[i+1] += ans[i]/10;
ans[i] %= 10;
}
}
// 输出高精度运算后的大数
void printBigData()
{
// 从最高位向后扫描,直到第 1 个非 0 数字出现
int p = NN-1;
while(ans[p] == 0) p--;
// 输出大数乘法运算的结果
while(p > -1) cout<<ans[p--];
cout<<"\n";
}
int main()
{
// 获取输入
string stra, strb;
cin>>stra>>strb;
// 执行大数乘法运算
getProduct(stra, strb);
// 输出
printBigData();
return 0;
}
【洛谷】 P1037 产生数
问题描述
两给出一个整数 n ( n < 1 0 30 ) n(n<10^{30}) n(n<1030)以及 k k k 个变换规则( k ≤ 15 k\le15 k≤15)。
规则:
- 一位数可变换成另一个一位数。
- 规则的右部不能为零。
例如: n = 234 n=234 n=234。有规则( k = 2 k=2 k=2):
2 → 5 2 \to 5 2→5
3 → 6 3 \to 6 3→6
上面的整数 234 经过变换后可能产生出的整数为(包括原数):
234
534
264
564
共 4 种不同的产生数。现在给出一个整数 n n n 和 k k k 个规则。求出经过任意次的变换(0 次或多次),能产生出多少个不同整数。仅要求输出个数。输入格式
第一行两个整数 n , k n, k n,k。
接下来 k k k 行,每行两个整数 x i , y i x_i, y_i xi,yi 。输出格式
输出能生成的数字个数。
输入样例
234 2
2 5
3 6输出样例
4
资源限制
时间限制:1.0s 内存限制:125.0MB
这道题实际上是求在特定变换规则下,一个指定数能有多少种变换。而该变换规则实际上就是规定一个数字能变换为哪些目标数字( 0 0 0 除外)。就像上面的例子,由于 2 2 2 能变换为 5 5 5(加上它自己本身,那么 2 2 2 就有两种变换: 2 → 2 2\to 2 2→2、 2 → 5 2\to 5 2→5),同理 3 3 3 也有两种( 3 → 3 3\to 3 3→3、 3 → 6 3\to 6 3→6);再看这个数字本身,其中含有 1 1 1 个 2 2 2、 1 1 1 个 3 3 3,那么不难算出其总的变换共有 2 × 2 = 4 2\times2=4 2×2=4 种。另外,还要注意数字变换中存在的传递关系,例如,对于变换 1 → 2 , 1 → 3 , 2 → 4 , 2 → 5 1\to 2, 1\to 3, 2\to 4, 2\to 5 1→2,1→3,2→4,2→5,这时候应得到:1 有五种变换规则( 1 → { 1 , 2 , 3 , 4 , 5 } 1\to \{1, 2, 3, 4, 5\} 1→{ 1,2,3,4,5}),2 有三种变换规则( 2 → { 2 , 4 , 5 } 2\to \{2, 4, 5\} 2→{ 2,4,5})。
所以,本题的总体求解思路如下:
由于数字一共有 10 个,因此可以用一个 10 × 10 10\times10 10×10 的二维矩阵来存放数字之间的可达关系,之所以选用邻接矩阵作为存放可达关系的数据结构是因为后续可以通过佛洛伊德算法来将这些间接的可达关系全部转换为直接可达,进而得到每个数的可变换数量(如 1 → 2 1\to2 1→2 且 2 → 3 2\to3 2→3,那么就有 1 → 3 1\to3 1→3)。
注:佛洛伊德算法是一个利用动态规划的思想来寻找给定的加权图中多源点之间最短路径的算法。因此当对一个带有权值的二维数组进行佛洛伊德算法后,它将得到这个二维数组所反映的图中各点的距离,也就反映了各点之间的可达关系。下面给出通过佛洛伊德算法来将规格为 10 × 10 10\times10 10×10 的二维矩阵 map 中所有间接可达的点变换为直接可达的代码:
void Floyd()
{
for (int k = 0;k < 10;k++)
for (int i = 0;i < 10;i++)
for (int j = 0;j < 10;j++)
if(map[i][k]&&map[k][j])
map[i][j]=1;
}
实际上统计变换个数很简单,只需要一层循环去遍历 n n n 中的每个数字,然后依次叠乘每个数字的可变换个数就能得到最终的变换总数。但是这里有个问题,考虑一种极端情况,假如每个数字之间都能相互变换(即每个数都有 10 种变换),那么对于一个长度为 30 的数字,其总的变换个数就为 1030,而这个数已经超过了 long,因此我们必须自己设计一个大数运算的算法,而不是直接用系统定义的数据类型进行乘法运算。
由于本题在进行叠乘时,总是一个不大于 10 的数字乘以之前的数,因此这里的大数相乘可以直接用当前的可变换数依次与前面的大数的每位进行乘法运算,然后再进行进位操作。算法如下:
for (i=0;i<len;i++) // len 表示当前输入的n的长度
{
for(j=0;j<31;j++) // 31是设置的最长的扫描距离,大于30即可
ans[j] *= change[n[i]-'0']; // ans是最终的结果数组
for(int j=0;j<31;j++)
{
if(ans[j] > 9)
{
ans[j+1] += ans[j]/10;
ans[j] %= 10;
}
}
}
下面给出基于以上思路得到的完整代码:
#include<iostream>
#include<cstring>
using namespace std;
int map[10][10]; // 用于弗洛伊德算法求每个数的可变换数量
int change[10]; // 用于统计每个数的可变换个数
int const MAXN = 10; // 最多可变换的数字
int const MAXL = 31; // 最终答案的最大长度(大于30即可)
void Floyd() // 弗洛伊德算法(求解每个数的可变换数量)
{
for (int k = 0;k < MAXN;k++)
for (int i = 0;i < MAXN;i++)
for (int j = 0;j < MAXN;j++)
if(map[i][k]&&map[k][j])
map[i][j]=1;
}
int main()
{
char n[MAXL]; // 由于输入的数据范围超过了C提供的最长数据类型,因此这里用字符串替代
int k; // 变换规则总数
while(cin>>n>>k)
{
int i,j=0,x,y;
for(i=0;i<k;i++)
{
cin>>x>>y;
map[x][y]=1; // 注意这里构建的map是有向的
}
Floyd(); // 求出可达矩阵
for(i=0;i<MAXN;i++) // 找出每个数的可变化数量,并将其存进数组change中
{
map[i][i]=1; // 注意自己和自己也算是一种可达
for(j=0;j<MAXN;j++)
if(map[i][j])
change[i]++;
}
int len=strlen(n); // 输入的字符串的长度
int ans[MAXL]={
0}; // 用于贮存最后的变化总数(即最终存放结果的数组)
ans[0]=1; // 必须将ans[0]初始化为1
for (i=0;i<len;i++) // 遍历输入的 n 以统计所有的变换个数
{
for(j=0;j<MAXL;j++) // 这里的循环结束条件只能是<MAXL,不能是sum[j]!=0,下同
ans[j] *= change[n[i]-'0'];
for(j=0;j<MAXL;j++) // 因为有可能中间某个数为0,此时若以sum[j]!=0作为循环结束
{
// 条件则会提前结束判断从而导致错误
if(ans[j] > 9)
{
ans[j+1] += ans[j]/10; //进位
ans[j] %= 10;
}
}
}
i=MAXL-1; // 重新将指针置于 ans 的最末尾之前(即最高位之前)
while(!ans[i]) i--; // 以找到第一个非零最高位
while(i>-1) cout<<ans[i--]; // 依次取出并输出
cout<<endl;
}
return 0;
}
文章浏览阅读645次。这个肯定是末尾的IDAT了,因为IDAT必须要满了才会开始一下个IDAT,这个明显就是末尾的IDAT了。,对应下面的create_head()代码。,对应下面的create_tail()代码。不要考虑爆破,我已经试了一下,太多情况了。题目来源:UNCTF。_攻防世界困难模式攻略图文
文章浏览阅读2.9k次,点赞3次,收藏10次。偶尔会用到,记录、分享。1. 数据库导出1.1 切换到dmdba用户su - dmdba1.2 进入达梦数据库安装路径的bin目录,执行导库操作 导出语句:./dexp cwy_init/[email protected]:5236 file=cwy_init.dmp log=cwy_init_exp.log 注释: cwy_init/init_123..._达梦数据库导入导出
文章浏览阅读1.9k次。1. 在官网上下载KindEditor文件,可以删掉不需要要到的jsp,asp,asp.net和php文件夹。接着把文件夹放到项目文件目录下。2. 修改html文件,在页面引入js文件:<script type="text/javascript" src="./kindeditor/kindeditor-all.js"></script><script type="text/javascript" src="./kindeditor/lang/zh-CN.js"_kindeditor.js
文章浏览阅读2.3k次,点赞6次,收藏14次。SPI的详情简介不必赘述。假设我们通过SPI发送0xAA,我们的数据线就会变为10101010,通过修改不同的内容,即可修改SPI中0和1的持续时间。比如0xF0即为前半周期为高电平,后半周期为低电平的状态。在SPI的通信模式中,CPHA配置会影响该实验,下图展示了不同采样位置的SPI时序图[1]。CPOL = 0,CPHA = 1:CLK空闲状态 = 低电平,数据在下降沿采样,并在上升沿移出CPOL = 0,CPHA = 0:CLK空闲状态 = 低电平,数据在上升沿采样,并在下降沿移出。_stm32g431cbu6
文章浏览阅读1.2k次,点赞2次,收藏8次。数据链路层习题自测问题1.数据链路(即逻辑链路)与链路(即物理链路)有何区别?“电路接通了”与”数据链路接通了”的区别何在?2.数据链路层中的链路控制包括哪些功能?试讨论数据链路层做成可靠的链路层有哪些优点和缺点。3.网络适配器的作用是什么?网络适配器工作在哪一层?4.数据链路层的三个基本问题(帧定界、透明传输和差错检测)为什么都必须加以解决?5.如果在数据链路层不进行帧定界,会发生什么问题?6.PPP协议的主要特点是什么?为什么PPP不使用帧的编号?PPP适用于什么情况?为什么PPP协议不_接收方收到链路层数据后,使用crc检验后,余数为0,说明链路层的传输时可靠传输
文章浏览阅读587次。软件测试工程师移民加拿大 无证移民,未受过软件工程师的教育(第1部分) (Undocumented Immigrant With No Education to Software Engineer(Part 1))Before I start, I want you to please bear with me on the way I write, I have very little gen...
文章浏览阅读304次。Thinkpad X250笔记本电脑,装的是FreeBSD,进入BIOS修改虚拟化配置(其后可能是误设置了安全开机),保存退出后系统无法启动,显示:secure boot failed ,把自己惊出一身冷汗,因为这台笔记本刚好还没开始做备份.....根据错误提示,到bios里面去找相关配置,在Security里面找到了Secure Boot选项,发现果然被设置为Enabled,将其修改为Disabled ,再开机,终于正常启动了。_安装完系统提示secureboot failure
文章浏览阅读10w+次,点赞93次,收藏352次。1、用strtok函数进行字符串分割原型: char *strtok(char *str, const char *delim);功能:分解字符串为一组字符串。参数说明:str为要分解的字符串,delim为分隔符字符串。返回值:从str开头开始的一个个被分割的串。当没有被分割的串时则返回NULL。其它:strtok函数线程不安全,可以使用strtok_r替代。示例://借助strtok实现split#include <string.h>#include <stdio.h&_c++ 字符串分割
文章浏览阅读2.3k次。1 .高斯日记 大数学家高斯有个好习惯:无论如何都要记日记。他的日记有个与众不同的地方,他从不注明年月日,而是用一个整数代替,比如:4210后来人们知道,那个整数就是日期,它表示那一天是高斯出生后的第几天。这或许也是个好习惯,它时时刻刻提醒着主人:日子又过去一天,还有多少时光可以用于浪费呢?高斯出生于:1777年4月30日。在高斯发现的一个重要定理的日记_2013年第四届c a组蓝桥杯省赛真题解答
文章浏览阅读851次,点赞17次,收藏22次。摘要:本文利用供需算法对核极限学习机(KELM)进行优化,并用于分类。
文章浏览阅读1.1k次。一、系统弱密码登录1、在kali上执行命令行telnet 192.168.26.1292、Login和password都输入msfadmin3、登录成功,进入系统4、测试如下:二、MySQL弱密码登录:1、在kali上执行mysql –h 192.168.26.129 –u root2、登录成功,进入MySQL系统3、测试效果:三、PostgreSQL弱密码登录1、在Kali上执行psql -h 192.168.26.129 –U post..._metasploitable2怎么进入
文章浏览阅读257次。本文将为初学者提供Python学习的详细指南,从Python的历史、基础语法和数据类型到面向对象编程、模块和库的使用。通过本文,您将能够掌握Python编程的核心概念,为今后的编程学习和实践打下坚实基础。_python人工智能开发从入门到精通pdf