小黄的刷题之路(七)——码题集OJ赛-复数求和

题目

复数求和问题

在这里插入图片描述

备注:复数按“最简”情况输出,系数请使用double类型,用cout或者printf(“%g”)输出。题目保证 1 ≤ n ≤ 100000 1\leq n\leq 100000 1n100000,每行输入非空字符串长度不超过1000,输入的浮点数最多精确到小数点后两位。


分析思路

思路:
  • 我们需要接收n行数据,每行都是一个复数,这个复数可能只有实数部分,可能只有虚数部分,可能都有。有一些甚至不是最简的未合并的复数,我们需要把这n行进行复数相加运算,结果输出一个最简复数
  • 很自然的我们可以想到,把实数部分和虚数部分分别相加,最后的输出结果再合在一起。为此定义变量real和imag
  • 我们首先需要解决的是如何接收输入的复数:对于输入的n行代表复数的数据,我们定义一个read()函数,不是用于读取每一行的复数,并合并为一个最简形式,然后n行累加。而是读取每一个实数或者虚数,然后累加到real或者imag上(因此某一行的数据可能调用read()函数多次)
  • 如何实现read函数每次读取一个实数或者复数呢,伪代码逻辑如下:
double read()
{
    getchar()读取一个字符;
    if(上一次read读到的不是换行符)读取上一次read的符号;
    while(读到的不是数字或者小数点)
    {
        if(读到'\n')上一个字符为\n不需要读取上次的符号(正负)
        else if(读到'-')后面的数字符号为负;
        else if(读到'+')后面的数字符号为正;
        else if(没有输入)结束
    }
    while(读到的是数字或者小数点)
    {
        计算这个数字是什么;
        继续读取下一个字符;
    }
    if(读到'i')
    {
        这是一个虚数
        返回这个虚数的数字部分,并标识这是一个虚数
    }
    else(读到的是'+''-''\n')
    {
        这是一个实数
        if('+'):下一个数字是正数,记录一下,下一次read要用到
        if('-'):下一个数字是负数,记录一下,下一次read要用到
        if('\n'):下一个次read是下一行的数了,不用记录符号
        if(没有输入或者遇到文件结束符)执行结束
        返回这个实数,并标识这是一个实数
    }
}

代码实现

3.1 C++代码实现
  • 快读

C++里的读入我们最熟悉的就是cin,cin的读取简单易懂,很适合初学者,但是有一个缺点就是慢,特别是在题目有时间限制的要求且有大量数据读入的情况下,就很容易超时

而C里面的scanf函数,读入速度比cin更快一点

具体原因:
scanf是格式化输入,printf是格式化输出。
cin是输入流,cout是输出流。效率稍低,但书写简便。
格式化输出效率比较高,但是写代码麻烦。
流输出操作效率稍低,但书写简便。

虽然scanf在大多数情况下满足对时间有要求的题目,但是难免有一些题目需要你把读入的速度和效率优化到极致,这个时候就需要用到快读

快读原理:单个字符的读入速度要比读入数字快,因此我们以字符的形式先读入,然后处理计算转为数字,快读比scanf还要快

inline int read(){
    int x=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9'){
        if(ch=='-')
            f=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
//重点在于这个语句,它等价于x = x*10 + (ch -= '0')
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    return x*f;
}

重点语句:x = (x << 1) + (x << 3) + (ch ^ 48)

<<是左移运算符,将原二进制数向左平移 x 位,右边原位置以 0 补齐。左移n为等价于乘以 2 n 2^n 2n,所以 (x << 1) + (x << 3) 等价于 x*10。而 ^是按位异或运算,等价于 ch = ch - ‘0’


  • OJ测评方式

OJ评测方式和表示结果

ASCII中的-1?-1的ASCII码是多少

在平台上有其自己的评测方式和数据输入方式,ASCII码中的-1代表文件结束符EOF,表示没有数据输入了,平台上需要用到这个来判断数据是否输入完成。

而在本地的VS上运行时情况就不一样了,我在自己本地的VS上运行,无法做到输入n行数据之后完成按下回车,然后触发到文件结束符的状态,接着退出快读的循环不再读取字符,输出最终结果。经过一番试探,我发现只有一种方法能够看到输出结果,那就是在输入n行数据并按下回车之后,按下**ctrl+C**便能触发文件结束符 - 1,接着便能看到输出结果,程序结束。

为了按下回车就能结束程序输出结果,我提供了另外一种表示输入数据完成的方式,那就是根据一开始输入的数据组数n,然后统计输入的字符里面换行符的个数,当换行符个数也达到n时说明不再输入了,跳出循环输出结果

#include<iostream>
#include<bits/stdc++.h> 
using namespace std;
bool flag = 0;//flag=0 real实数部分;flag=1 imag虚数部分;
bool flag_exit = 0;//没有输入时置1
int pre_f = 0;//标志上一次read()函数最后读到的符号
int nl_count = 0;//统计换行符\n

double read() {//快读(每一次只读一个实数或者一个虚数)
    flag = 0;//flag=0先默认是实数,后面如果遇到i再改为flag=1表示虚数
    char ch = getchar();
    int f = 1;//统一默认正数,如果读到'-'再改
    //把上一次read到的符号还回来
    if (pre_f != 0)f = pre_f;//pre_f=0表示前一个字符是\n,上一次read最后读到的是\n
    
    while (ch < '0' || ch > '9')//非数字
    {//上一次如果read的是虚数,读到i之后就退出函数返回了,在这次getchar()才有可能读到'\n'
        //但如果上一次read的是实数,这一次数字前的+-\n在上个read()末尾已经被读走了
        //所以实际上只有上一次read的是虚数,才有可能触发这个ch=='\n'
        if (ch == '\n')f = 1;//上一个字符为\n不需要读取上次的符号(正负)
        else if (ch == '-')f = -1;//负
        else if (ch == '+')f = 1;//正
        else if (ch == 'i') {//如果读到的是i或者-i直接返回,根本不会执行到下面
            flag = 1;
            return 1 * f;//单独一个i,需要系数 1
        }
        else if (ch == -1) {//EOF = -1(文件结束符),即没有输入
            flag_exit = 1;
            break;
        }
        ch = getchar();//继续读入下一个字符并处理
    }//读到数字跳出循环
    
    bool dot = 0;
    int x = 0, m = 0;
    while ((ch >= '0' && ch <= '9') || ch == '.') //数字
    {//n = 0代表读入整数;n = 1代表读入小数
        if (ch == '.')dot = 1;
        else if (dot == 0)x = (x << 1) + (x << 3) + (ch^48);//记得加括号
        //<<是左移运算符,左移1位相当于乘2,左移3位相当于乘8,^是按位异或运算,作用相当于ch-='0'
        //上下两个表达式等价,上面的写法更快更好
        else {x = x * 10 + (ch-='0'), m++;}
        //最终将得到的x乘以10的-m次幂,还原成真实的数x
        ch = getchar();
    }//读到非数字跳出循环
    
    //返回此次read的结果(一个虚数)
    if (ch == 'i') {//imag
        flag = 1;
        return (pow(0.1, m) * x) * f;
    }
    //如果是实数,一定是因为读到下一个数前的'+/-'或者换行符才能跳出while循环,这个符号需要被记录供下次read使用,所以有必要设置一个全局变量pre_f来记录符号
    //而如果是虚数,读到i我们就知道得结束返回了,并不会提前读出下一个数前的'+ - \n',所以这里用不到pre_f
    else {//real 返回这次的结果(一个实数),同时记录下一个read的数字的符号
        if (ch == '+')pre_f = 1;
    	else if (ch == '-')pre_f = -1;
    	else if (ch == '\n'){pre_f = 0;nl_count++;}
    	else if (ch == -1)flag_exit = 1;
        
        flag = 0;
        return (pow(0.1, m) * x) * f;
    }
}

int main()
{
    int n;
    cin >> n;
    getchar();//清除输入输入缓冲区
    double number = 0;
    double real = 0;//实数部分
    double imag = 0;//虚数部分
    while (1)
    {
        number = read();//每一次只读一个实数或者一个虚数
        if (flag)imag += number;
        else real += number;
        if (flag_exit)break;
        if(nl_count == n)break;//已经输入了n组数据
    }

    //输出,要注意分情况
    if (real && imag)//既有实数,又有虚数,要有“+ -”
    {
        if (imag == 1)
            cout << real << "+i";
        else if (imag == -1)
            cout << real << "-i";
        else if (imag > 0 && imag != 1)
            cout << real << '+' << imag << 'i';
        else if (imag < 0 && imag != -1)
            cout << real << imag << 'i'; 
    }
    else if (!real && imag)//没有实数只有虚数,不用“+”
    {
        if (imag == 1)
            cout << "i";
        else if (imag == -1)
            cout << "-i";
        else
            cout << imag << 'i';
    }
    else if (real && !imag)//没有虚数只有实数
        cout << real;
    else if (!real && !imag)//实数和虚数都为0,输出0
        cout << 0;

    return 0;
}

一直在想能不能把第一个while循环的【if (ch == ‘\n’)f = 1;】去掉,但是想到一个反例:

第一行:3 - 2i

第二行:2 + i

可以看到第一行数据调用了两次read()函数,第一次read()已经让全局变量pre_f = -1(因为读到了下一个数字前的符号是负号),第三次read的时候 if (pre_f != 0)f = pre_f; 所以f = -1而此时getchar()读到的是’\n‘(因为上一次read的数是-2i,是虚数,并不会抢先读取)进到第一个while循环里面,如果不加上【if (ch == ‘\n’)f = 1;】数字2前的符号是正的,也就是本来f应该等于1,现在却等于 -1,所以这个判断很有必要


3.2 python代码

python版本的代码在平台上是无法通过的,可能因为备注上要求系统用double,输出用cout或者printf(“%g”)

但是在其他IDE上运行的结果是正确的

def main():
    n = int(input())
    Rsum = 0
    Csum = 0
    for i in range(n):
        cnum = input()
        cnum = cnum.replace('-', '+-')
        a = cnum.split('+')
        for k in a:
            if 'i' in k:
                if k == 'i':
                    k = float(1)
                elif k == '-i':
                    k = float(-1)
                else:
                    k = k.replace('i', '')
                    k = float(k)
                Csum += k
            else:
                k = float(k)
                Rsum += k


    if Csum == 0:  # 纯实数
        print("{:.2f}".format(Rsum))
    elif Rsum == 0:  # 纯虚数
        print("{:.2f}i".format(Csum))
    elif Csum > 0:  # 实部和序部均不为0
        print("{} + {:.2f}i".format(Rsum, Csum))
    else:
        print("{:.2f} {:.2f}i".format(Rsum, Csum))
    pass

if __name__ == '__main__':
    main()

顺便分享一下python自带的复数类型

a = 6 + 0.6j
print(type(a))
print(a) 
print(a.real)
print(a.imag)
a = complex(1, 2)
print(a)
b = complex(1)
print(b)
c = complex("1")
print(c)
d = complex("1+2j")
print(d)
"""输出结果:
<class 'complex'>
(6+0.6j)
6.0
0.6
(1+2j)
(1+0j)
(1+0j)
(1+2j)
"""

总结

这道题的关键点在于

  • 快读
  • 每次读一个实数或者虚数
  • 符号如何处理
  • 读取到下一个字符前的符号怎么传递给下一次read()

⭐感谢您能看到这里,我会好好努力继续分享好的文章的!⭐

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
杭州电子科技大学在线评测系统(杭电OJ)中的题目1000-1100是一系列编程题,我将分别进行回答。 1000题是一个简单的入门题,要求计算两个整数的和。我们可以使用一个简单的算法,读取输入的两个整数,然后将它们相加,最后输出结果即可。 1001题是一个稍微复杂一些的题目,要求实现字符串的逆序输出。我们可以使用一个循环来逐个读取输入的字符,然后将这些字符存储在一个数组中。最后,我们可以倒序遍历数组并将字符依次输出,实现字符串的逆序输出。 1002题是一个求最大公约数的问题。我们可以使用辗转相除法来解决,即先求出两个数的余数,然后将被除数更新为除数,将除数更新为余数,直至两个数的余数为0。最后的被除数就是最大公约数。 1003题是一个比较简单的排序问题。我们可以使用冒泡排序算法来解决,即每次比较相邻的两个元素,如果它们的顺序错误就交换它们的位置。重复这个过程直至整个数组有序。 1100题是一个动态规划问题,要求计算给定序列中的最长上升子序列的长度。我们可以使用一个数组dp来保存到达每个位置的最长上升子序列的长度。每当遍历到一个位置时,我们可以将其和之前的位置比较,如果比之前位置的值大,则将其更新为之前位置的值加1,最后返回dp数组的最大值即可。 以上是对杭电OJ1000-1100题目的简要回答,涉及了一些基本的编程知识和算法思想。希望对您有所帮助。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值