题目链接:牛客周赛Round 51 D
周日的时候没仔细读,今天重看发现a是比b大很多的,算是一个高精度除法的问题,读入采用string类型,对于string类型的处理就是用一个char类型的变量去遍历,减去字符0得到数字值,从高位到低位转化刚好就是字符串的0到末尾,按顺序取出转化成数字,先前已经转化的部分乘以10再加上新转化的数字(就相当于在之前的数字末尾再加一位),每次转化都及时对b取模,最后得到与b规模相近的数值,使用__gcd(a, b)函数求得最大公因数。
梳理一下步骤:
- 较大的a按string类型读入,较小的b就按long long类型读;
- 遍历a,对其进行逐位还原,以string str = ‘123’为例, s t r [ 0 ] = ′ 1 ′ str[0] = '1' str[0]=′1′, s t r [ 0 ] − ′ 0 ′ = 1 str[0] - '0' = 1 str[0]−′0′=1(整数1), 1 × 10 + s t r [ 1 ] − ′ 0 ′ = 12 1\times10 + str[1] - '0' = 12 1×10+str[1]−′0′=12,以此类推还原a;
- 还原过程中的取余:模拟除法过程,我们在手算除法的时候就是先从被除数的前几位找除数的倍数进行商余操作的,在计算机中这一步就是取余的操作;这一步的依据是辗转相除法,假设有
a
、
b
、
q
、
r
a、b、q、r
a、b、q、r四个自然数,且
0
<
=
r
<
b
0 <= r < b
0<=r<b,有
g
c
d
(
a
,
b
)
=
g
c
d
(
b
,
r
)
gcd(a, b) = gcd(b, r)
gcd(a,b)=gcd(b,r),即
a
a
a和
b
b
b的最大公因数与
b
b
b和
r
r
r的最大公因数相等。基于此我们每还原出一位就对
b
b
b进行一次取余的操作,应用上面的公式就可以较小的代价等价求得公因数。
最终通过的代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod = 998244353;//常用质数
//gcd(a, b) = gcd(a-b,b) = gcd(a-2b,b) = gcd(a%b, b)辗转相除,知道a与b差不多大
int main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);//关闭输入输出流的同步,提高程序运行效率
string str;
ll b;
cin>>str>>b;
ll ans = 0;
for(char i:str)
{
ans = (ans * 10 + (i - '0')) % b;
}
cout<<__gcd(ans, b)<<'\n';//关闭输入输出流同步后不能再使用endl
return 0;
}
这次代码是看了题解的,也参考了很多别人的写法,学到了一些新东西:
- 竞赛常用模数这个算复习了,这个数其实还是在int范围内,一个较大的质数,但在32位有符号整数表示范围内(int, − 2 31 ∼ 2 31 − 1 -2^{31}\sim2^{31}-1 −231∼231−1即 − 2 , 147 , 483 , 648 ∼ 2 , 147 , 483 , 647 -2,147,483,648\sim2,147,483,647 −2,147,483,648∼2,147,483,647),但是有乘法运算时容易溢出,尽量使用64位有符号整数(long long, − 2 63 ∼ 2 63 − 1 -2^{63}\sim2^{63}-1 −263∼263−1即 − 9 , 223 , 372 , 036 , 854 , 775 , 808 ∼ 9 , 223 , 372 , 036 , 854 , 775 , 807 -9,223,372,036,854,775,808\sim9,223,372,036,854,775,807 −9,223,372,036,854,775,808∼9,223,372,036,854,775,807);
- __gcd(x,y)函数:在头文件#include中,竞赛一直用万能头文件<bits/stdc++.h>但是还是要稍稍记几个常用头文件;求整数 x 、 y x、y x、y的最大公因数;
- for(auto elem:range):c++11中的for range循环,以前常写C++代码的时候没怎么见人用过,这次翻题解发现大家都在用,确实很简洁干净,这里有几个小点:一个是auto关键字,用于两种情况:(a)生命变量时根据初始化表达式自动推断该变量的类型(b)声明函数时函数返回值的占位符;这段代码里char就可以替换为auto;for range循环的形式有三种:
(1) for(auto elem:range):创建range的拷贝,遍历时无法修改range中的元素;(满足本题需求)
(2) for(auto& elem:range):不创建range的拷贝,可以直接修改range中的元素,但一般用for(auto&& elem:range)
(3) for(const auto& elem:range):不创建range的拷贝,只读range中的元素。 - 关闭输入输出流的同步,提高程序输入输出的效率(避免Time Limit Exceeded错误):
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
第一小句解除cin和cout的绑定,让它们可以独立缓冲;cin.tie(0) 指的是解除cin与cout的同步。在标准C++中,cin与cout的效率之所以低,是因为要先把输出的东西存入缓存区再进行输出,默认时cin绑定的是cout,每次执行<<运算符时都要调用flush,增加IO负担。这样做可以得到与scanf和printf接近的效率。使用时要注意的是关闭了同步流,不能使用scanf和printf,不能使用cout<<endl(换成cout<<‘\n’),不能使用getchar()函数。
差不多就这些了,主要参考的博客地址也列一下:
关闭输入输出流_1
关闭输入输出流_2
C++ 11 的for range写法
__gcd(x, y)函数用法及朴素实现