# OJ多组和单组输入输出

# 📑 目录

[TOC]

# 1.OJ中多组单组输入输出

# 1.1.循环I/O处理FAQ

  • 多组输入和输出的做法在ACM和各大OJ也是很常见这些,现在企业招聘使用的一些平台很多也是支持这样的。
  • 碎碎念:笔者以前的时候,第一次接触接触这种输入输出的时候,很是不适应,但是后来,看到下面的一段文字才深刻理解了,单组输入输出和多组输入输出的联系。后来我就没有以前那么纠结,每次做题都想这题到底有没有按暗示我多组输入输出。后来我想通了之后的做法,就是,能用多组输入输出的我也懒得用单组输入输出了,因为多组的方式也能AC掉单组的,但是反之不一定,原因见下面的两端文字吧:
    • 1、为什么需要循环输入输出:通常来说 OJ 对于每道题里面有.in.out文件,分别表示测试数据的输入和输出。如果某些编程题的所有数据都只做在一个.in 一个.out 中,这样就会变成多组测试了,所以需要提交的代码中循环处理。
    • 2、处理方法:其实这个问题可以避免,就是编程题后台每个样例做一组对应的.in.out 文件,这样就变成单组测试,代码就不需要循环处理,但是平时练习的题目质量不一,这个问题都会出现。
    • 解决方案:代码里面循环处理了即使是单组测试也会完全没问题,所以为了偷懒,可以全写成循环处理

在线评测系统,你的程序的输入和输出是相互独立的(也就是说,一个在.in文件,输出会重定向到另一个.out文件),因此,每当处理完一组测试数据,就应当按题目要求进行相应的输出,而不必将所有结果存储起来一起输出。

# 1.2.常见语法

#include <stdio.h>
int main() {
    int a,b;
    while(scanf("%d%d",&a, &b) != EOF)	//注意while处理多个case
        printf("%d\n",a+b);
    return 0;
}
1
2
3
4
5
6
7

但是我本人,还喜欢这种方式

#include <stdio.h>
int main() {
    int a,b;
    while(~scanf("%d%d",&a,&b))	//注意while处理多个case
        printf("%d\n",a+b);
    return 0;
}
1
2
3
4
5
6
7

解释:while(~scanf("%d%d",&a,&b))while(scanf("%d%d",&a, &b) != EOF)是等效的 ~是按位取反,scanf的返回值是输入值的个数,如果没有输入值就是返回-1 而我们知道-1的补码表示就是全1,按位取反显然就是0

//特别的,还有在多组输入同时还要求a或者b不为0的

#include<cstdio>
int main()
{
	int a,b;
	while(scanf("%d%d",&a,&b)!=EOF&&(a!=0||b!=0))
	{
		printf("%d\n",a+b);
	}
	return 0;
}
1
2
3
4
5
6
7
8
9
10
#include <iostream>
using namespace std;
int main() {
    int a,b;
    while(cin >> a >> b)//注意while处理多个case
        cout << a+b << endl;
}
1
2
3
4
5
6
7

# 1.3.笔试题技巧✔️

  • 做了好多牛客类型的,发现有的题目可能就是需要用『找规律』的思想去做。
  • 不要老是想着暴力,太限制自己的思考了

# 1.4.多组输入输出的坑

  • 如果测试数据是多组的,但是恰巧你代码里面需要一些标记数组,比如map,set啥的。
  • 在循环内一定的记得清空,不然可能会产生:前面的测试样例影响了后续数据的答案这种情况。

# 1.5.关于输出格式的坑

  • 1、行末空格:比如,我输出需要打印多个数需要使用空格分隔的时候,我们循环使用printf("%d ",x)这种会很方便,但是这样会导致行末多1个空格,后台系统会严格比对你的输出和.out文件,这样也会被判错误
  • 2、换行问题:对于每个样例,建议输出完全之后都换行一下。对于一些题目,可能就是不换行就导致了后面输入数据错位,那就肯定不可能过了

# C和C++混合I/O难点辨析

# 3.1.辨析单引号和双引号

  • 单引号:'3'代表的是“字符”3
  • 双引号:"3"代表的是“字符串”3

『字符』和『字符串』在C语言和C++中的区别和联系

  • 字符: C语言和C++中都是用char来承载,C++中string类,[]运算符返回的也是char
  • 字符串: C语言中,用字符数组来承载 C++中用string类来承载 为了能够将C++中其他STL和string类的配合到极致,我们需要非常熟悉C语言风格的字符数组和C++的string类的互相转化。

# 3.2.常见数字格式—输入输出

输入 输出
int %d %d
long long %lld %lld
double %lf %lf

# 3.3.字符串输入输出

注意:

字符串是个逻辑概念

  • 纯C语言中,用字符数组来承载它,也就是我们所说的具体实现
  • 纯C++写法中,用string来承载它。PS:string底层封装了字符数组,但是比较复杂,初学C++不需要深究,不看源代码,无法理解。

我们只考虑,C语言的输入输出。

C++的string,请用cin啥的。

输入 输出
字符串 %s %s
字符 %c %c

字符和字符串的差别,在第1讲讲了。难点在字符串用“字符数组承载”

# 1.用scanf

  • scanf的%s

输入:是识别到' '(空格)或者'\n'(换行)就不要了,然后在扫描进的后面自动加上'\0'

输出:是识别到'\0'就输出完毕

#include<bits/stdc++.h>
using namespace std;

static const int maxn=1e5+5;
int solve[maxn];

int main()
{
    scanf("%s",solve);
    //第1组测试123 24343
    //第2组测试23134131
    
    printf("%s",solve);
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  • scanf的%s是以 空白符(包括空格)来进行截断的!
  • 注意,在读入n之后,要使用getchar来接收后面的换行符,否则会使得for循环内的gets读入这个换行符,导致第1个字符串读取错误。

# 2.用gets

char test[105];
int i=0;
while(NULL!=gets(test)) 
{
    ++i;
}
1
2
3
4
5
6
C编译器 C++
PAT/PTA 允许用 ❎由于可以利用该函数进行『缓冲区溢出攻击』
牛客 / ❎由于可以利用该函数进行『缓冲区溢出攻击』

gets输入一行,无论中间会不会有空格, 它识别到'\n'就不要了

  • Tips:由于,gets这个函数可以进行“缓冲区溢出”攻击,在很多平台开始禁用!

PTA和牛客对比注意:

1.PAT上数据比牛客上严格
2.PAT上2020年有一些题目的边界数据的增加,有的教程上代码无法AC了

# ⭐️用2种方法解决gets被禁用

  • PAT的这两个C++编译器(C++ gcc6.5.0C++ clang++ 6.0.1)都不支持gets()
//要是你的C++代码中出现了,就会导致(似乎原因是什么不安全...)    
a.cpp: In function ‘int main():
a.cpp:12:11: error: ‘gets’ was not declared in this scope
  gets(str1);
1
2
3
4
  • 『注意』:PAT上面的C语言编译器支持gets()(可以用C语言编译器),如果你不想用C++的STL的话

# ⭐️[首选方案1]:getline(cin, first);✔️

  • 我最常用的!
  • 如我写的这个[题解的代码](
C++代码
string first;
while( getline(cin, first) )
{
    cout<<first<<endl;
}
1
2
3
4
5
Java代码
string first;
while( getline(cin, first) )
{
    cout<<first<<endl;
}
1
2
3
4
5

# 坑-使用getline一般需要和getchar配合-2023年-荣耀机考

int main() {
    long long n;
    while (scanf("%lld", &n)) {
        if (n <= 0) {
            return 0;
        }
        map<string, bool> mp;
        vector<node> solve;
        string str;
        getchar(); //⭐️需要借助这个获取'\n'
        while (n--) {
            getline(cin, str);
            if (false == mp[str]) {
                //去重
                solve.push_back(node(str, get_time_check(str)));
                mp[str] = true;
            }
//            cout << "--" << str << endl;
//            getchar();
        }

        sort(solve.begin(), solve.end(), cmp);
        int len = solve.size();
        for (node val: solve) {
            cout << val.str << endl;
        }
    }

    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

# ⭐️方案2:cin.getline✔️

  • 使用 iostream库中的cin.getline函数代替gets
  • 『使用比方法1麻烦点』
/* 读入一行(可含空格),直到换行符结束
 * 将其前num-1个字符存入数组a中并以字符c结尾 */
cin.getline(a, num, c);
解决方式:
使用 iostream 库中的cin.getline函数代替gets
tips:
1、也可以不传入第三个参数c,则默认 '\0' 结尾
2、若num大于所读入的字符数,则直接存入整行字符串,再在末尾加入字符c结尾
    
比如
    cin.getline(ch2,6);//在不遇到结束符的情况下,最多可接收6-1=5个字符到ch2中 
1
2
3
4
5
6
7
8
9
10
11
  • 演示如下:
char solve[20];
//注意,下面我们用的是20,但是实际上获得的是19个,因为最后那个被识别到的'\0'也要识别进去
//然后,
while( cin.getline( solve, 20) )
{
    printf("%s\n",solve);
    printf("Ok\n");
}
1
2
3
4
5
6
7
8

# [代码] OJ多组输入输出『分语言』

# 4.1.纯C++语言版(不准用C语言版本)

  • 为了能够熟练的使用C++的STL
  • 由于自己熟练C语言的字符数组,思维方式快成纯C语言的了,但是这些很影响刷OJ做题的速度。
  • 所以,打算,使用C++中的String代替字符数组
  • 弃用字符数组。

原因

比如字符查找,我就可以懒得写了
字符匹配,也懒得写
1
2
  • 1、整型数据
#include<iostream>
using namespace std;

int main()
{
	int a,b;
	
	while(cin>>a>>b)
	{
		cout<<a+b<<endl;
	}
	return 0;
} 
1
2
3
4
5
6
7
8
9
10
11
12
13

严格的

这样的格式

3,6
1
  • //cin.get()使用比较难讲,不要赖用
#include<iostream>
using namespace std;

int main()
{
	int a,b;
	char c;
	while((cin>>a)&&(c=cin.get())&&(cin>>b))
	{
		cout<<a+b<<endl;
        cout.put(c);
	}
	return 0;
} 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
  • 2、字符数组(C++中用string类——自己不准自己用字符数组)

注意,这种输入,下面这样的数据,注意也用这组数据测试3)

asdad asdsa
1
#include<iostream>
#include<string> 
using namespace std;

int main()
{
	string str;
	while(cin>>str)
	{
		cout<<str<<endl;
	}
	
	return 0;
} 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
  • 3、字符数组(C++中用string类)两个-用空格分开
#include<iostream>
#include<string> 
using namespace std;

int main()
{
	string one;
	string two;
	while(cin>>one>>two)
	{
		cout<<one<<endl;
		cout<<two<<endl;
	}
	
	return 0;
} 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  • 4、整行字符(string),包括空格『学习记忆』
#include<iostream>
#include<string> 
using namespace std;

int main()
{
	string one;
	string two;
	//未考查下面这种多组输入 
	while(getline(cin,one))
	{
		cout<<one<<endl;
		
	}
	
	return 0;
} 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 4.2.纯C语言

  • 0、单个字符

  • 1、整型数据

#include<stdio.h>

int main()
{
	long long int a,b;
	while(~scanf("%lld%lld",&a,&b)) 
	{
		printf("%lld\n",a+b);
	}
	return 0;
}
1
2
3
4
5
6
7
8
9
10
11
  • 2、字符数组
#include<stdio.h>

int main()
{
	char test[100];
	while( ~scanf("%s",test) ) 
	{
		printf("%s\n",test);
	}
	return 0;
}
1
2
3
4
5
6
7
8
9
10
11
  • 3、字符数组两个-用空格分开
aa ss
1
#include<stdio.h>

int main()
{
	char one[100];
	char two[100];
	
	while(~scanf("%s%s",one,two)) 
	{
		printf("%s 中间加上我 %s\n",one,two);
	}
	return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
  • 4、整行字符,包括空格
#include<stdio.h>

int main()
{
	char test[100];
	
	while(NULL!=gets(test)) 
	{
		printf("%s\n",test);
	}
	return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12

# 『多组输入输出』OJ使用经验

# 2.1.如何搞定超时?估计需要的算法时间复杂度✔️

方法1:

  • Q:如何知道自己代码是否太慢了,超时? 背景:实际编码的时候,大家其实都不会去马上分析时间复杂度,可能是先写完再看会不会超时,那就产生上面的问题。
    A:可以用大的边界的数据,最耗时的那种
    1 1000000000000,本地,自我运行了一次,发现,2秒没出来,很显然超时,毕竟比赛的也就1-2秒,测一下也不掉肉

方法2:技巧:看需要处理的数据的大小——判断能不能使用暴力

  • 关于『时间复杂度』分析
  • 通常来说:以『C/C++为标准』的,一般的系统『1s』能够跑的算法量级是『不足108』的,所以做题的时候评估算效率很重要
  • 直接判断你的做法能不能通过(也就是能不能暴力去解决),其他语言自己乘个时间倍数
  • 一般情况下,对于大部分OJ,可以假设1秒钟可以完成10^8的计算量,那么对于一个规模为10^3的数据,你可以采取复杂度为O(N^2)的算法,但当规模上升到10^5时,采取O(N^2)的算法就会得到一个TLE(超时)的结果。

重要经验:重要经验

  • 比如题目中n=105
  • 那么,我们就可以很敏感的知道我们的算法需要一个O(n)或者O( nlog(n) )
  • 『平方复杂度直接ByeBye』
吐槽:这一点,在最开始学习计算机的时候没有理解那么清晰,现在很清楚了!!
上面的经验是很宝贵的!!!
1
2

例题:

01.OJ时间复杂度

02.OJ时间复杂度

  • 如上:
数据是5次方,你如果循环2层,也就是平方复杂度,那直接凉凉,但是暴力有分。。。实在走投无路可以弄
- 写暴力不一定得满分,但是可能有50%啥的,,比如上题在『2021年第3次模拟』过的样例7/12,获得分数50%

PS:我当时做该题,就是考虑到了无法直接暴力,所以使用了其他算法:2个前缀和
- 1、前缀和统计<=当前数字的有多少个元素
- 2、前缀和,统计<=当前数字的元素和
然后,将整体求和,然后利用上面将数字分割为Less和Up级

其他算法:下标和元素绑定在一起排序
1
2
3
4
5
6
7
8
9

# 2.2.常见边界数据

  • 1、数据全相同
  • 2、全0
  • 3、元素无
  • 4、空指针
  • 5、奇/偶测试数据

# 2.3.什么叫特判『Special judge』✔️

  • 大概意思是:题目同样的样例,你的输出可能和题目不一样也没所谓
  • 也就是你输出的结果可以和题目中样例输出结果不一样,但是是对的。
  • 反正答案对就行,比如某个矩阵,横竖斜和是15,答案不唯一,你只要写出一个也OK

特判底层原理:将你的输出数据,输入去检测,而不是一般的比对

  • 在幻方矩阵一题中,还有那个所谓的(特殊判断)
  • 笔者在,2020年(即2021届秋招)腾讯笔试中遇见了

HDOJ上的解释是:

  • 对于有多解的题目或者是对于浮点型答案需要另加精度判定的题目会有special judge
  • 即OJ判题者会另写一个程序来判定你给出的解是否可行(对于多解的题目)或者给出的解是否在规定的范围内(对于浮点型输出需要精度判定的题目)。

# 2.4.『多组输出输出』常见坑:防踩坑

  • 1、循环输入输出:会发生一个问题(十分常见!!),如果测试数据是多组的,但是恰巧你代码里面需要些标记数组,map,set 等,在循环内一定记得清空,不然可能会产生前面的测试样例影响了后续数据的答案。
  • 2、一般多组输出,记得要换行,printf("%d\n",sum);
  • //第一次的时候,由于没有加\n,OJ告诉我是,答案错误
  • //加了\n就对了

# 2.6.MLE

  • Memory Limit Exceeded (MLE) : 内存开的过大

  • 一般情况下,对于online judge而言静态数组可以开到K*106大小,K在5左右。

  • Runtime Error (RE) :运行出现问题,可能是以下情况:.

    • *ACCESS_VIOLATION* 访问越界,多数情况为数组开小了。
    • *INTEGER_DIVIDE_BY_ZERO* 出现0为除数的情况。
    • *STACK_OVERFLOW* 栈溢出。
    • *......* Other runtime errors.

# 2.7.关于cin的速度

  • 对于大部分国内OJ而言,cin,cout在没有加一些**黑科技(其实,就是那个同步。。HACV)**之前的读入输出速度会比scanf,printf慢,建议尽量使用scanf,printf;

# 参考资料

# ⭐️[训练]牛客多组I/O