一、[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(t≤10),表示数据组数。接下来 t t t 行,每行一个正整数 n ( n ≤ 1000 ) n(n \leq 1000) n(n≤1000) 和数码 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 2≤N≤10 或 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
0→len−1):
- 将
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;
}