CSP-J冲刺训练营(2):高精度计算真题

一、[NOI] 高精度除以高精度(整除)

这种类型的题目非常麻烦,一步一步推出来,加油!

#include <iostream>
#include <string>
using namespace std;
 
string a, b; // 高精度数

// 高精度减法
string subtract(string a, string b)
{
    string result = "";
    int carry = 0;
 
    // 从最低位开始逐位相减
    while (!a.empty() || !b.empty())
    {
        int digit_a = 0, digit_b = 0;
        if (!a.empty()) 
        {
            digit_a = a.back()-'0';
            a.pop_back();
        }
        if (!b.empty())
        {
            digit_b = b.back()-'0';
            b.pop_back();
        }
 
        int diff = digit_a-digit_b-carry;
        if (diff < 0)
        {
            diff += 10;
            carry = 1;
        }
        else
        {
            carry = 0;
        }
 
        // 将计算得到的差值加入到结果中
        result = to_string(diff) + result;
    }
 
    // 去除结果中的前导零
    while (result.front()=='0' && result.length()>1)
        result = result.substr(1);
    return result;
}
 
// 判断a是否小于b
bool isLess(string a, string b)
{
    if (a.length() < b.length())
        return true;
    else if (a.length() > b.length())
        return false;
    else
        for (int i = 0; i < a.length(); i++)
            if (a[i] < b[i])
                return true;
            else if (a[i] > b[i])
                return false;
    return false;
}

// 判断是否s为零
bool isZero(string s)
{
    for (int i = 0; i < s.length(); i++)
        if (s[i] != '0')
            return false;
    return true;
}
 
// 高精度除法
string divide(string a, string b)
{
    string ans = ""; // 商
    string rem = ""; // 余数
 
    for (int i = 0; i < a.length(); i++)
    {
        if (isZero(rem)) rem = a[i];
        else rem += a[i];
        
        int cnt = 0;
        
        // 不断减去除数,直到无法继续减去为止
        while (!isLess(rem, b))
        {
            rem = subtract(rem, b);
            cnt++;
        }
        
        // 将当前的商的位数加入到商中
        ans += to_string(cnt);
    }
 
    // 去前导零
    while (ans.front()=='0' && ans.length()>1)
        ans = ans.substr(1);
    return ans;
}

int main()
{
    cin >> a >> b;
    if (b == "1")
    {
        cout << a;
        return 0;
    }
    cout << divide(a, b);
    return 0;
}

二、[普及] 阶乘数码

1. 审题

题目描述

选自 某谷题目
n ! n! n! 中某个数码出现的次数。

输入格式

第一行为 t ( t ≤ 10 ) t(t \leq 10) t(t10),表示数据组数。接下来 t t t 行,每行一个正整数 n ( n ≤ 1000 ) n(n \leq 1000) n(n1000) 和数码 a a a

输出格式

对于每组数据,输出一个整数,表示 n ! n! n! a a a 出现的次数。

样例 #1

样例输入 #1

2
5 2
7 0

样例输出 #1

1
2

2. 思路

主要是阶乘的代码可能有点烦,要做很多次高精度乘法。核心代码如下,没有思路的同学先看看,开窍了取试试写,实在不会再看参考答案哦,相信你自己!

此题比较适合用 char[](反正对了就行)。

void fac(int n)
    for (int i = 2; i <= n; i++) // 阶乘
        int carry = 0;
        for (int j = 0; j < len; j++) // 高精度乘单精度
            ans[j] = ans[j]*i+carry;
            carry = ans[j]/10;
            ans[j] %= 10;
        while (carry > 0) // 处理最高位
            ans[len] = carry%10;
            len++;
            carry /= 10;

3. 参考答案

#include <bits/stdc++.h>
using namespace std;

int T, n, m;
int len, cnt;
int ans[1000005];

void fac(int n)
{
    for (int i = 2; i <= n; i++) // 阶乘
    {
        int carry = 0;
        for (int j = 0; j < len; j++) // 高精度乘单精度
        {
            ans[j] = ans[j]*i+carry;
            carry = ans[j]/10;
            ans[j] %= 10;
        }
        while (carry > 0) // 处理最高位
        {
            ans[len] = carry%10;
            len++;
            carry /= 10;
        }
    }
}

int main()
{
    cin >> T;
    while (T--)
    {
        // 输入
        cin >> n >> m;
        
        // 初始化
        ans[0] = 1;
        len = 1;
        cnt = 0;
        
        // 计算
        fac(n);
        
        // 统计个数
        for (int j = 0; j < len; j++)
            if (ans[j] == m)
                cnt++; // 统计个数
        cout << cnt << endl;
    }
    return 0;
}

3. [NOIP1999 普及组] 回文数

1. 审题

题目描述

某谷 过不去喽
若一个数(首位不为零)从左向右读与从右向左读都一样,我们就将其称之为回文数。
例如:给定一个十进制数 56 56 56,将 56 56 56 65 65 65(即把 56 56 56 从右向左读),得到 121 121 121 是一个回文数。
又如:对于十进制数 87 87 87
STEP1: 87 + 78 = 165 87+78=165 87+78=165
STEP2: 165 + 561 = 726 165+561=726 165+561=726
STEP3: 726 + 627 = 1353 726+627=1353 726+627=1353
STEP4: 1353 + 3531 = 4884 1353+3531=4884 1353+3531=4884
在这里的一步是指进行了一次 N N N 进制的加法,上例最少用了 4 4 4 步得到回文数 4884 4884 4884
写一个程序,给定一个 N N N 2 ≤ N ≤ 10 2 \le N \le 10 2N10 N = 16 N=16 N=16)进制数 M M M 100 100 100 位之内),求最少经过几步可以得到回文数。如果在 30 30 30 步以内(包含 30 30 30 步)不可能得到回文数,则输出 Impossible!

输入格式

两行,分别是 N N N M M M

输出格式

如果能在 30 30 30 步以内得到回文数,输出格式形如 STEP=ans,其中 ans \text{ans} ans 为最少得到回文数的步数;否则输出 Impossible!

样例 #1

输入

10
87

输出

STEP=4

2. 思路

这道题目乍眼看上去好像不用高精度,但是说了是 N N N 进制数 M M M!如果不相信,来看看没有用高精度的惨状:
请添加图片描述
接下来理思路:

  • 输入 N , M N,M N,M
  • 迭代 30 30 30 次:
    • 将字符串 M M M 反转并赋值给变量 r m rm rm(为了加法时从右向左)
    • 初始化变量 carry 0 0 0,用于保存进位
    • 初始化空字符串 newm,用于保存新的数值结果
    • 计算 M M M r m rm rm 的长度的较大值,并将其保存在变量 len
    • 循环对于每个位置 j j j 0 → l e n − 1 0\to len-1 0len1):
      • carry 的值加到 sum
      • M [ j ] M[j] M[j] r m [ j ] rm[j] rm[j] 的字符转换为对应的整数值加到 sum
      • 计算余数并赋值给 rem
      • 计算进位并赋值给 carry
      • rem 转换为字符串并添加到 newm
    • 如果还有进位的值 carry,将其转换为字符串并添加到 newm
    • 判断 newm 是否为回文数,如果是,则输出当前迭代的步数并结束程序
    • 更新 M M M 的值为 newm
  • 如果最大迭代步数后仍然没有找到回文数,则输出 "Impossible!"

一定记得判断十六进制!不然就会有一个样例过不了!!!

3. 参考答案

#include <iostream>
#include <string>
#include <algorithm>
using namespace std;

int n, rem;
string m, rm, newm;

// 判断是否回文
bool isPalin(string s)
{
    int len = s.length()-1;
    for (int i = 0; i <= len / 2; i++)
        if (s[i] != s[len-i])
            return false;
    return true;
}

int main()
{
    // 输入
    cin >> n >> m;
    
    int len = m.length();
    
    // 特例
    if (isPalin(m))
    {
        cout << "STEP=0";
        return 0;
    }
    
    for (int i = 1; i <= 30; i++) // 进行最多30次迭代
    {
        rm = m;
        reverse(rm.begin(), rm.end()); // 反转字符串
        
        int carry = 0;
        int newLen = max(len, (int)rm.length());
        for (int j = 0; j < newLen; j++)
        {
            int sum = carry;
            
            // 处理 16 进制字母
            if (isdigit(m[j])) sum += m[j]-'0';
            else sum += m[j]-'A'+10;
            if (isdigit(rm[j])) sum += rm[j]-'0';
            else sum += rm[j]-'A'+10;
            
            rem = sum%n; // 计算余数
            carry = sum/n; // 计算进位
            newm += rem >= 10 ? rem - 10 + 'A' : rem + '0';
        }
        if (carry)
        {
            newm += carry >= 10 ? carry - 10 + 'A' : carry + '0';
            newLen++;
        }
        
        if (isPalin(newm)) // 判断新的m是否为回文
        {
            cout << "STEP=" << i;
            return 0;
        }
        m = newm;  // 更新m的值
        len = newLen;
        newm = "";
    }
    cout << "Impossible!";  // 迭代30次后未找到回文
    return 0;
}
  • 11
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值