题目
复数求和问题
备注:复数按“最简”情况输出,系数请使用double类型,用cout或者printf(“%g”)输出。题目保证 1 ≤ n ≤ 100000 1\leq n\leq 100000 1≤n≤100000,每行输入非空字符串长度不超过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测评方式:
在平台上有其自己的评测方式和数据输入方式,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()
⭐感谢您能看到这里,我会好好努力继续分享好的文章的!⭐