1 条题解

  • 0
    @ 2025-8-24 21:02:45

    自动搬运

    查看原文

    来自洛谷,原作者为

    avatar wangbinfeng
    今天搞完大概就永远不会碰 OI 了,大家祝好!

    搬运于2025-08-24 21:02:44,当前版本为作者最后更新于2021-07-03 11:19:57,作者可能在搬运后再次修改,您可在原文处查看最新版

    自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多

    以下是正文



    $ \color{#00B8D4} \rule{2pt}{44pt} \color{#E5F8FB} \rule[24pt]{365pt}{20pt} \color{#e8e8e8}\rule{0.5pt}{44pt} \color{#f5f5f5}\rule{0.5pt}{44pt} \color{#fafafa}\rule{0.5pt}{44pt} \kern{-365pt}\kern{-1.5pt} \color{#bfbfbf}\rule[0pt]{365pt}{0pt}\kern{-365pt} \color{#d6d6d6}\rule[-0.5pt]{365pt}{0pt}\kern{-365pt} \color{#ececec}\rule[-1pt]{365pt}{0pt}\kern{-365pt} \color{#f8f8f8}\rule[-1.5pt]{365pt}{0pt}\kern{-365pt} \color{black} \raisebox{24pt}{ \raisebox{6pt}{ \kern{-1pt} \color{#00B8D4}\large{\kern{2pt}\bf{i}\kern{5.5pt}} \raisebox{1.5pt}{ \color{#404040}\footnotesize \kern{-4pt}\sf\bf{作者提示} }}}\kern{-200pt}$. $\kern{1pt}\raisebox{10pt}{\footnotesize\sf{\color{black}{本题删除了争议数据,但是处于严谨的考虑还是建议学会本题解提供的做法。具体可点击本提示查看。}}}$

    本题的本意想考察各种语言输出时控制小数位数,但是这种做法本来就是错误的,且目前已有的题解也全部是严重错误的,因此我重构了题解。不过在这篇题解中,我想讲一下题目的本意,再讲正确的做法。首先你应当会控制输出时的小数位数,具体可以前往我写的 B2021 题的题解查看题目本意要求的做法(请注意,你只应当查看我写的题解,而暂时不要阅读其它题解,因为全部是错误的,具体理由可以看这里

    我们考虑到将 B2021 的代码直接带到这里,并且修改以下内容即可 AC 本题 (目前题目数据是错误的,如果你 WA 且仅 WA 掉了 #4,那么恭喜你做对了本题)

    1. float 修改为 long double
    2. epseps1×1091\times10^{-9} 改为 1×10151\times10^{-15}
    3. 将输出时的格式符改掉,具体地,对于 C 语言为 printf("%.12Lf", f)
      对于 C++ 语言为 cout << fixed << setprecision(12) << f
      对于 Python 语言为 print(f"{f:.12f}")
      对于 java 语言为 String formattedF = String.format("%.12f", f)
      对于 Pascal 语言为 Str(f:0:12, formattedString)

    咋一看这么做好像挺对的,毕竟考虑过浮点数的误差了。但是考虑这个数据:0.499999999...(暂时保留一位小数),数学上的答案显然应该是 0.4,但是由于误差和 epseps 的原因很可能被代码误认为是 0.5。那么有没有办法处理这种数据呢?答案是有的。因为输入的数据是十进制,那么保证了十进制数是不存在误差的。那么误差的来源就是进制转换,即我们不能在代码中有进制转换出现。

    那么,float/double/long double 等自带的浮点数显然就不能用了,我们只能自己写一个浮点数处理的程序。存储输入的浮点数我们可以采用一个数组来存储每一位的数值(我的代码中使用了字符串,实际上它们的作用是相同的),然后对这个数组进行操作。

    全部的流程如下,读者可以把本题当成一个普通的模拟题来完成:

    1. 如果输入的是负数,可以把负号提取出来,然后就不存在负数了。
    2. 如果输入的是整数,可以考虑直接输出,也可以考虑用第 3. 步的方法。
    3. 若输入的小数位数不足 1212 位,则补足 1212 位(当然也可以补充更多的位数,比如为了方便判断四舍五入而补足 1313 位)。
    4. 判断小数部分第 1313 位的数值,如果大于等于 55 则向上进一位,否则不对上一位产生影响。
    5. 若第 1212 位进位变成了 1010,但由于十进制中没有 1010 这个数字(数字只单独一位的数值),将这一位补零并向上一位进位。
    6. 重复第 5. 步直到没有任何一位存在 1010
    7. 注意小数部分最高位进位需要进到整数部分,而整数部分最高位如果进位会导致结果答案变长。注意特判即可。

    这道题可以练习大家的代码功底,作者本人暂且只提供 C++ 语言的代码,其他语言请大家自行练习。

    #include<bits/stdc++.h>
    using namespace std;
    string s;
    int i;
    bool flag_zero,flag_fu,flag_jw;
    signed main(){
    	cin>>s;
    	for(i=0;s[i];i++)
    		if(s[i]=='.')break;
    	if(i==s.length()){
    		cout<<s<<'.';
    		for(int i=1;i<=12;i++)cout<<'0';
    		return 0;
    	}
    	if(s[0]=='0')flag_zero=true;
    	if(s[0]=='-')s[0]='0',flag_fu=true;
    	for(int j=1;j<=14;j++)s+='0';
    	if(s[i+13]>='5')s[i+12]++;
    	for(int j=i+12;j>i;j--)if(s[j]>'9')s[j]='0',s[j-1]++;
    	if(s[i]>'.')s[i]='.',s[i-1]++;
    	for(;i>=0;i--)if(s[i]>'9'){
    		if(i==0)flag_jw=true;
    		else s[i-1]++;
    		s[i]='0';
    	}
    	if(flag_fu)cout<<'-';
    	if(flag_jw)cout<<"10";
    	else if(flag_zero)cout<<'0';
    	for(i=(s[0]=='0');s[i]!='.';i++)cout<<s[i];
    	cout<<'.';
    	for(int j=1;j<=12;j++)cout<<s[i+j];
    }
    
    • 鸣谢:
      1. @wangbinfeng 首先确定本题数据点的错误位置及原因,两次重构本题解(这是第三版题解)。
      2. @N_z_ 指出上一版题解的错误并且给出了本题解使用的正确的做法的思路。
      3. 阅读我的题解的大家。

    发现上面的签名是动图了吗?\color{grey}{\tiny{\texttt{发现上面的签名是动图了吗?}}}
    • 1

    信息

    ID
    6879
    时间
    1000ms
    内存
    128MiB
    难度
    1
    标签
    递交数
    4
    已通过
    3
    上传者