NEUQ-ACM预备队week1-乒乓球+高精度

前言

作为一名初涉算法的初学者,学业不精,思路粗糙,万望指点、优化以及相互交流沟通学习

乒乓球问题的题目与C++解

题目详情

题目描述:

国际乒联现在主席沙拉拉自从上任以来就立志于推行一系列改革,以推动乒乓球运动在全球的普及。
其中11分制改革引起了很大的争议,有一部分球员因为无法适应新规则只能选择退役。
华华就是其中一位,他退役之后走上了乒乓球研究工作,意图弄明白11分制和21分制对选手的不同影响。
在开展他的研究之前,他首先需要对他多年比赛的统计数据进行一些分析,所以需要你的帮忙。
华华通过以下方式进行分析,首先将比赛每个球的胜负列成一张表,然后分别计算在11分制和21分制下,双方的比赛结果(截至记录末尾)。 
比如现在有这么一份记录,(其中W表示华华获得一分,L表示华华对手获得一分): 
WWWWWWWWWWWWWWWWWWWWWWLW 
在11分制下,此时比赛的结果是华华第一局11比0获胜,第二局11比0获胜,正在进行第三局,当前比分1比1。
而在21分制下,此时比赛结果是华华第一局21比0获胜,正在进行第二局,比分2比1。
如果一局比赛刚开始,则此时比分为0比0。 
你的程序就是要对于一系列比赛信息的输入(WL形式),输出正确的结果。

输入格式:

每个输入文件包含若干行字符串(每行至多20个字母),字符串由大写的W、L和E组成。
其中E表示比赛信息结束,程序应该忽略E之后的所有内容。

输出格式:

输出由两部分组成,每部分有若干行,每一行对应一局比赛的比分(按比赛信息输入顺序)。
其中第一部分是11分制下的结果,第二部分是21分制下的结果,两部分之间由一个空行分隔。

输入样例:

WWWWWWWWWWWWWWWWWWWW
WWLWE

输出样例:

11:0
11:0
1:1

21:0
2:1

说明:

在一场乒乓球比赛中,当且仅当一人分值高于11/21分且较另外一人高至少两分,结束比赛,即判胜。
题目解读
难点:
在输入格式中,我们特别的关注到:每行至多20个字母,也就是说,会有额外的换行符‘\n’输入。我们似乎无法解决这个问题,我们既不能使用二维数组通过for循环输入解决(未知输入几行),也似乎不能使用cin,scanf()这些会跳过空格和换行符的输入方式,于是,我们想到了使用getchar()进行一个伪数据判断。
算法描述
定义一个较长的字符数组,使用getchar()及伪数据判断接受全部的输入。
利用for循环遍历这个字符数组,遇到’W’华华得一分,遇到’L’对手得一分。
根据题意进行格式控制–当一局结束后(一次判胜)输出一次结果,当比赛结束(数组遍历结束)输出当前场次的结果。

代码实现

#include<iostream>
using namespace std;
char a[1000000];
int main()
{
    int i = 0;
    a[i] = getchar();//伪数据判断输入,利用getchar()不会因为换行和空格结束输出的特点,选择'E'
                     //作为输入结束的判定
    while (a[i] != 'E')
    {
        i++;
        a[i] = getchar();
    }
    int huahua = 0, duishou = 0;
    for (int j = 0; j < i; j++)//i记录的是不包括'E'的长度,E输入后并没有进行i++
    {
        if (a[j] == 'W')huahua++;
       else if(a[j]=='L')duishou++;
       //如果有判胜负则输出
        while (huahua >= 11 && huahua - duishou >= 2 || duishou >= 11 && duishou - huahua >= 2)
        {
            printf("%d:%d\n", huahua, duishou);
            huahua = 0, duishou = 0;
        }
    }
    //输出遍历结束后两人当前赛局并控制格式
    printf("%d:%d\n", huahua, duishou);
    printf("\n");
    huahua = 0, duishou = 0;
    for (int j = 0; j < i; j++)
    {
        if (a[j] == 'W')huahua++;
        else if(a[j]=='L')duishou++;
        while (huahua >= 21 && huahua - duishou >= 2 || duishou >= 21 && duishou - huahua >= 2)
        {
            printf("%d:%d\n", huahua, duishou);
            huahua = 0, duishou = 0;
        }
    }
    printf("%d:%d", huahua, duishou);
    return 0;
}

补充:伪数据判断/输入值判断

使用场景:

未知将要输入多少个数据,但是知道输入结束的标志符。注意,如果空格和换行都算作输入结束时,用一般的cin或者scanf()基本上都可以完成这个任务,这里的输入结束符应该是一个比较特殊的情况,如需要输入空格,以回车结束,或者是以题目给定的,例如本题’E’这种符号。

代码实现:

char a[100000],int i=0;
     a[i] = getchar();//利用输入值来判断是否停止输入,所以要先进行一次输入
    while (a[i] != 'E')
    {
        i++;
        a[i] = getchar();
    }

高精度加法的C++解决办法

题目详情

题目描述:

对于输入的两个不超过100位数的非负整数,给出两数之和。

输入格式:

在两行中分别给出两个不超过100位数字的非负整数。

输出格式:

在一行中输出两数之和。

输入样例:

123
12

输出样例

135

题目解读:

难点:

相比于常规的A+Bproblem,此题非常规的点是这两个给定的A,B是不超过100位数字的非负整数,两个数据大小都超过了C及C++中的所有数据类型,作为不具备高精度加法的编程语言,我们就无法简单以A+B去解决这个问题。

解题思路:

在这里插入图片描述

利用小学二年级知识,我们可以逐位逐位相加,满十进一。由此,解题思路应运而生。

算法描述

  1. 利用两个字符数组接受输入的数字字符。
  2. 将字符串数组转换为整型数组并倒转数组。
  3. 逐位相加,并使用一个数组存储各位相加的数字。遵循满十进一规则。
  4. 输出结果。

代码实现

#include<iostream>
#include<cstring>
using namespace std;
int main()
{
    char A[100] = { '\0' }, B[100] = { '\0' };
    cin >> A;
    cin >> B;
    //分别计算字符数组A,B的长度a,b并记录一个较大值c
    int a1 = strlen(A);
    int b1 = strlen(B);
    int c1 = a1 >= b1 ? a1 : b1;
    int a[100] = { 0 }, b[100] = { 0 };
    for (int i = 0; i < a1; i++)
    {
        a[i] = A[a1 - 1 - i] - 48;//实现字符向数字转换 并倒转整个数组
    }
    for (int i = 0; i < b1; i++)
    {
        b[i] = B[b1 - 1 - i] - 48;
    }
    int sum[101] = { 0 };//为什么sum的数组要多开一个长度呢?
    for (int i = 0; i < 100; i++)
    {
        int item = a[i] + b[i];
        sum[i] += item % 10;//对十取余,正常获取每位数字(即便它不需要进位)
        sum[i + 1] = item / 10;//若满十则进一
    }
    if (sum[c1] != 0)//当在最高位进一时,若sum定义仍为一百的长度,可能会造成溢出,无法存储进位。
    {
        cout << sum[c1];
    }
    for (int i = c1 - 1; i >= 0; i--)//从高位到低位依序输出
    {
        cout << sum[i];
    }
    return 0;
}

问题:为什么统计sum时一个使用+=一个使用=?

 for (int i = 0; i < 100; i++)
    {
        int item = a[i] + b[i];
        sum[i] += item % 10;//对十取余,正常获取每位数字(即便它不需要进位)
        sum[i + 1] = item / 10;//若满十则进一
    }

因为,进位时,下一位永远是0(数组已全部初始化置0),我们可以简单地给它赋值为1,实现的操作与+=1相同;

然而,在计算当前位数时,有可能在计算上一位数时完成了进位操作,直接赋值就将上一次的进位1抵消掉了。

原题链接

https://www.luogu.com.cn/problem/P1601

高精度求累加和的问题与解

题目详情

题目描述:

使用求和公式求1到N的累加和大家都会,但是如果把N值变大呢,比如100位的整数,那该怎么求?

输入格式:

输入在一行中给出1个位数不超过100位的整数N。

输出格式:

对每一组输入,在一行中输出1+2+3+……+N的值。

输入样例:

在这里给出一组输入。例如:

10

输出样例:

在这里给出相应的输出。例如:

55

解题思路:

鉴于数字过大,简单的使用循环进行前N项和的求解显然是不可解的,因此,很容易想到使用高斯求和公式对简单的1+2+3+…+n问题求解(即等差数列前N项和公式)。

涉及相关知识:
高精度乘法、高精度数除低精度数。

代码实现:

#include<iostream>
#include<cstring>
using namespace std;
char A[101] = { '\0' };
int main()
{
    cin >> A;
    int along = strlen(A);

    int a[100] = { 0 }, b[101] = { 0 },sum[220] = { 0 };
//字符型向整型数组转化,并倒置数组
    for (int i = 0; i < along; i++)   a[i] = A[along - 1 - i] - 48;
    for (int i = 0; i < along; i++)  	b[i] = A[along - 1 - i] - 48;
    //执行首项+末项
    b[0]++;
    //判断是否发生进位
    for (int i = 0; i < along; i++)
    {
        b[i + 1] += b[i] / 10;
        b[i] = b[i] % 10;
    }
//计算(首项+末项)*项数
//这里只能确定a数组的长度为along b数组的长度是along还是along+1不确定
//虽然可以通过编写代码确认具体长度,但是,鉴于已经初始化整个数组为0
//我们无妨让最终结果的某一位再加上一个0
    for (int i = 0; i < along; i++)
        for (int j = 0; j < along + 1; j++)
            sum[i + j] += a[i] * b[j];
//判断sum的进位情况
    for (int i = 0; i < 220; i++)
    {
        int tmp = sum[i];
        sum[i] = tmp % 10;
        sum[i + 1] += tmp / 10;
    }
  //记录sum的有效长度(去掉前导0)同时,在本题背景中,无需担心最后结果是0。
    int sumlen = 220;
    while (sum[sumlen - 1] == 0)sumlen--;
    //进行除2操作(高精度数除低精度数)
    for (int i = sumlen - 1; i >= 0; i--)
    {
        int temp = sum[i];
        sum[i] = temp / 2;
        //需要注意:如果是高位余数,我们应该扩大十倍补至下一位(即落位)
        if (i > 0)
        {
            sum[i - 1] += 10 * (temp % 2);
        }
        //但是,如果余数本身就是个位产生的,我们要记得把余数原原本本地还给个位哟
        else if (i == 0)
        {
            sum[i] = temp / 2;
            sum[i] += temp % 2;
        }
    }
    //可惜,当这样做完除法后,我们又无法确定最后的sum有多少位了
    //所以,依然要考虑去除前导0的情况
    
    int flag = 0;//利用flag记录是否有过至少一次输出
    for (int i = sumlen - 1; i >= 0; i--)
    {

        if (sum[i] == 0 && flag == 0);
        else
        {
            flag = 1;
            printf("%d", sum[i]);
        }
    }
    return 0;
}

后记:

在最后输出前导0时,考虑到最后结果不会为0,可以更加优化这个去除前导0的步骤

int flag = 0;
for (int i = sumlen - 1; i >= 0; i--)
{
    if (sum[i] != 0 || flag != 0)
    {
        printf("%d", sum[i]);
        flag = 1;
    }
}
return 0;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值