第一章习题题解
文章目录
一、比赛技巧
1.1 std::ios::sync_with_stdio(false),tie(0)及其局限性
std::ios::sync_with_stdio(false)可以用来提高效率
在c++中之所以cin,cout效率低,是因为先把要输出的东西存入缓冲区,再输出,导致效率降低,而这段语句可以来打消iostream的输入和输出缓存,可节省时间,使效率与scanf与printf相差无几,还有应注意的是scanf与printf使用的头文件应是stdio.h而不是 iostream。
一般搭配tie函数使用
tie 函数
tie是将两个stream绑定的函数,空参数的话返回当前的输出流指针。在ACM里,经常出现 数据集超大造成 cin TLE的情况。我们可以在IO之前将stdio解除绑定
在默认的情况下cin绑定的是cout,每次执行 << 操作符的时候都要调用flush,这样会增加IO负担。可以通过tie(0)(0表示NULL)来解除cin与cout的绑定,进一步加快执行效率。
对上面的“绑定”的解释
用linux下watch命令(windows下还不知道有没有什么类似的东西,如果没有就用本办法:手动关掉再打开)实时察看test.txt文件,会发现每当你在终端里敲几个字后按下回车,test.txt文件里的文字就多了几个。
而如果你将代码中标注的那一行注释掉,就会发现,test.txt只有在程序运行结束(linux下按ctrl+d,windows下是ctrl+z结束输入)后才会有东西出现。
这就是“绑定”的效果,每当被“绑定”的对象有出入或输出操作,就会自动刷新“绑定”的对象的缓冲区,以达到实时的效果。局限性
使用std::ios::sync_with_stdio(false)后,cin(),cout()不能与printf(),scanf()混合使用。结合的用法
std::ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
1.2 cstring与string是两个不同的头文件
首先说,在C++中,#include "iostream"
与#include "iostream.h"
的区别:
iostream是C++的头文件,iostream.h是C的头文件,即标准的C++头文件没有.h
扩展名;而将以前的C的头文件转化为C++的头文件后,有时加上c的前缀表示来自于c,例如cmath
就是由math.h
变来的,或者直接不加。
一般C里带“.h”扩展名的库文件,比如iostream.h
,在新标准后的C++标准库中都有一个不带“.h”扩展名(iostream)的与之相对应,区别除了后者的好多改进之外,还有一点就是后者的东东都塞进了std
名字空间中。
但唯独string
很特别
C++在头文件中定义了string
类,但问题在于C++要兼容C的标准库,而C的标准库里碰巧也已经有一个名字叫做string.h
的头文件,这个头文件跟C++的string类并没有什么关系,所以并非的“升级版本”,他们是毫无关系的两个头文件。
所以区别就是:
头文件 | 描述 |
---|---|
string.h | 旧的C 头文件,对应的是基于char*的字符串处理函数; |
string | 包装了std 的C++头文件,对应的是新的string 类; |
cstring | 对应于旧C 头文件的std 版本。 |
另外
CString
是MFC
中定义的字符串类,MFC中很多类及函数都是以CString为参数的,另外CString类重载了LPCSTR
,(即const char*)运算符,所以如果你在MFC下面使用CString类,就可以直接用CString类做为参数来调用需要一个C语言风格字符串的WinAPI函数,编译器会自动调用LPCSTR
成员函数完成从CString到一个C风格字符串的转换。如果你在MFC下使用C++语言中标准的string类,那么在调用需要C语言风格的字符串为参数的WinAPI时,你必须显示调用string.c_str()成员函数,来完成同样的转换,也就是说在MFC里,如果用CString类,会比sting类方便那么一点点。
cstring代表的是string.h,但是被封装到了std里面,譬如调用strlen函数,需要写成std::strlen(yourstr)才行,这个使用方法比较符合C++的标准要求string就是C++标准库里面的string模板(确切地说应该是一个特化的模板),但是他同样包含了C风格字符串操作函数的定义(应该是通过包含string.h实现的)string.h就不需要使用名字空间了,这个是C风格字符串操作的一个函数库,strlen,strcpy,strcat,strcmp……都在这里面了,不过既然是C风格的库,当然不需要namespace支持了。
1.3 关于memset
用memset需要包含<cstring>
头文件
最好不要任意赋值,详细用法
1.4 数组初始化问题
常常看到一些题解没有初始化数组,有些则有,原则如下:
整型数组
- 全局数组,未初始化时,默认值都是 0;
- 局部数组,未初始化时,默认值为随机的不确定的值;
- 局部数组,初始化一部分时,未初始化的部分默认值为 0;
char 型数组
- 全局数组,未初始化的部分,默认值为 ‘ ’ ;
- 局部数组,初始化一部分后,未初始化部分默认值为 ‘ ’ ;
- 局部数组,未初始化时,默认值不可预知。
double ,float 型数组
- 全局数组,未初始化时,默认值都是 0.0;
- 局部数组,未初始化时,默认值为随机的不确定的值;
- 局部数组,初始化一部分时,未初始化的部分默认值为 0.0;
bool 型数组
- 全局数组,未初始化时,默认值都是 0;
- 局部数组,未初始化时,默认值为 204;
- 局部数组,初始化一部分时,未初始化的部分默认值为 0;
1.5 关于字符串比较大小问题
C++中字符串分两种,一种是C语言的字符串,一种是string字符串。
C语言字符串是不可以直接比较大小的,string是可以直接比较大小的。
char型可以做加减按照ascii
但是试了一下似乎只是不能直接在cout << a1<b1 << endl;
要用一个变量保存a1<b1
的值就可以比较了,对于字符的比较都没报错
对于字符串的比较下string没有warning而char则有
具体细节:
- C语言的字符串的实质是一个字符数组中存储的字符序列,如果直接比较大小相当于比较了两个字符串的首地址的大小,毫无意义。
- C语言的字符串需要通过strcmp函数进行比较大小。
- C++添加的string字符串是一个类,该类对运算符>、<和==进行了重载,能够直接比较两个字符串的大小。(运算符重载技术)
- 建议在C++中尽量使用string字符串,简单,且不容易出错。
1.6 迭代器的本质
起源于看到string中的begin()
1、总的说:
1)范围——指针属于迭代器的一种(指针可以用来遍历容器[数组])
2)功能——迭代器有着比指针更细的划分并对应能力不同的功能(重载不同的运算符)
3)行为——迭代器比指针更统一和良好的用法(更轻易使用begin()和end(),不用担心越界)。
2、迭代器:
1)迭代器不是指针,是类模板,表现的像指针。模拟了指针的一些功能,通过重载了指针的一些操作符,->,++ --等封装了指针,是一个“可遍历STL( Standard Template Library)容器内全部或部分元素”的对象, 本质是封装了原生指针,是指针概念的一种提升(lift),提供了比指针更高级的行为,相当于一种智能指针,他可以根据不同类型的数据结构来实现不同的++,–等操作;
2)迭代器返回的是对象引用,而不是对象的值,cout只能输出迭代器使用 * 取值后的值,不能直接输出自身;
3)能一次访问容器中的各个元素,通过迭代器,容器和算法可以结合起来,对算法给与不用的迭代器,就可以对不同容器进行相同的操作。
3、指针:
指针能指向函数,迭代器不行,只能指向容器,指针只能指向某些特定容器。
4、迭代器使用后就释放了,不能再继续使用,指针能。
1.7 关于string和ascii
//这种情况打印不出
string tmp="";
tmp =1+"a";
cout << tmp << endl;
//这种情况可以打印出
string tmp="";
tmp =1+'a';
cout << tmp << endl;
//这会报错
string tmp =1+'a';
cout << tmp << endl;
//因此可能初始化比较重要
1.8 单引号和双引号的区别
-
单引号是字符型,单引号引起的一个字符实际上代表一个整数。
-
双引号是字符串型,双引号引起的字符串,代表的却是一个指向无名数组起始字符的指针。该数组会被双引号之间的字符以及一个额外的二进制为零的字符 ‘\0’ 初始化。
-
"a"和’a’的区别,前者是字符串,后者是字符。
实际上 “a” 是 “a\0”,以’\0’结尾。而’a’单单表示a这个字符。
字符串可以是"abcde"这样的表示多个字符的一个组合,但是’abcde’这样就是错误的!!!总结一下,通常情况下单个字符用字符,多个字符组合起来就是字符串
二、糖糖别胡说我真的不是签到题
链接:https://ac.nowcoder.com/acm/contest/20960/1021
题目描述:
从前,有 nnn 只萌萌的糖糖,他们分成了两组一起玩游戏。他们会排成一排,第 iii 只糖糖会随机得到一个能力值 bib_ibi。从第 iii 秒的时候,第 iii 只糖糖就可以消灭掉所有排在他前面的和他不是同一组的且能力值小于他的糖糖。
为了使游戏更加有趣,糖糖的爸爸,娇姐,会发功 mmm 次,第 iii 次发功的时间为 cic_i ci,则在第 cic_ici 秒结束后,b1,b2,…,bcib_1,b_2,…,b_{c_i}b1,b2,…,bci都会增加 1.
现在,娇姐想知道在第 nnn 秒后,会有多少只糖糖存活下来。
输入描述:
第一行只有一个整数 T(T<6)T(T<6)T(T<6),表示测试数据的组数。
第二行有两个整数 n,mn,mn,m。表示糖糖的个数以及娇姐发功的次数。(1≤n≤50000,1≤bi≤10000001 \leq n\leq 50000,1 \leq b_i \leq 10000001≤n≤50000,1≤bi≤1000000)
第三行到 n+2n+2n+2 行,每行有两个整数 ai,bia_i,b_iai,bi,表示第 iii 只糖糖属于那一组以及他的能力值。(0≤ai≤1,1≤bi≤10000000\leq a_i \leq 1,1\leq b_i\leq 10000000≤ai≤1,1≤bi≤1000000)
第 n+3n+3n+3 行到第 n+m+2n+m+2n+m+2 行,每行有一个整数 cic_ici ,表示GTW第 iii 次发功的时间.(1≤ci≤n1\leq c_i \leq n1≤ci≤n)
输出描述:
总共T行,第i行表示第i组数据中,糖糖存活的个数。
输入
1
4 3
0 3
1 2
0 3
1 1
1
3
4
输出
3
题解:
#include <iostream>
#include <cstring>
using namespace std;
struct tang
{
int a;
int b;
};
int t,n,m,x;
struct tang queues[50002];
int ps[50002];
int main()
{
//提高cin和cout的效率
std::ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin>>t;
while(t--){
cin>>n>>m;
//初始化为0
memset(ps,0,sizeof(ps));
for(int i=1;i<=n;i++){
cin>>queues[i].a>>queues[i].b;
}
for(int i=1;i<=m;i++){
cin>>x;
ps[x]+=1;
}
//进行倒的差分
for(int i=n;i>=1;i--){
ps[i]+=ps[i+1];
queues[i].b+=ps[i];
}
int max0=0,max1=0;
int result;
int count=0;
//分情况讨论,总的是倒着开始比较
for(int i=n;i>=1;i--){
if(queues[i].a==0){
max0=max(queues[i].b,max0);
if(max1>queues[i].b){
count++;
c++ }
}
else{
max1=max(queues[i].b,max1);
if(max0>queues[i].b){
count++;
}
}
}
result=n-count;
cout << result << endl;
}
return 0;
}
三、奇妙拆分
题目描述:
在遥远的米♂奇♂妙♂妙♂屋里住着一群自然数,他们没事就喜欢拆♂开自己来探♂究。现在他们想知道自己最多能被拆分成多少个不同的自然数,使得这些自然数相乘的值等于被拆分的数。
输入描述:
第1行输入一个整数T,代表有T组数据。
第2-T+1行,每行输入一个整数n,代表需要被拆分的数。
数据保证:0<T≤100,0<n≤109。
输出描述:
输出一共T行,第i行输出一个整数,代表第i行输入的n最多可以被拆分成多少个不同的自然数。
示例1
输入
3
1
4
12
输出
1
2
3
说明
1可以被拆分为:1
4可以被拆分为:1*4(1*2*2是不允许的,因为有重复的数)
12可以被拆分为:1*2*6或1*3*4
示例2
输入
1
114514
输出
4
说明
114514可以被拆分为:1*2*31*1847
题解:
#include <iostream>
#include <cmath>
using namespace std;
int t,x;
int main()
{
cin >> t;
while(t--){
cin >> x;
//注意从2开始
int count=2;
if (x==1){
cout << 1 << endl;
}
else{
//注意细节不能i<=sqrt(x)
//另外x一直在变化的所以sqrt后的值也在变
for (int i=2;i<sqrt(x);i++){
if (x%i==0){
count++;
x=x/i;
}
}
cout << count << endl;
}
}
return 0;
}
四、数学考试
链接:https://ac.nowcoder.com/acm/contest/20960/1022
题目描述:
今天qwb要参加一个数学考试,这套试卷一共有n道题,每道题qwb能获得的分数为ai,qwb并不打算把这些题全做完,
他想选总共2k道题来做,并且期望他能获得的分数尽可能的大,他准备选2个不连续的长度为k的区间,
即[L,L+1,L+2,…,L+k-1],[R,R+1,R+2,…,R+k-1](R >= L+k)。
输入描述:
第一行一个整数T(T<=10),代表有T组数据
接下来一行两个整数n,k,(2<=n<=200,000),(1<=k,2k <= n)
接下来一行n个整数a1,a2,...,an,(-100,000<=ai<=100,000)
输出描述:
输出一个整数,qwb能获得的最大分数
示例1
输入
2
6 3
1 1 1 1 1 1
8 2
-1 0 2 -1 -1 2 3 -1
输出
6
7
题解:
#include <iostream>
using namespace std;
int t,n,k;
//用于存放每道题的分数
int a[200005];
//用于存放前缀和
long long int nn[200005];
int main()
{
cin >> t;
while(t--){
cin >> n >> k;
for (int i=0;i<n;i++){
cin >> a[i];
}
nn[0]=0;
//求前缀和,这里i<=n注意了
for (int i=1;i<=n;i++){
nn[i]=nn[i-1]+a[i-1];
}
long long int max0=-1e11;
long long int max1=-1e11;
//注意前缀和相减时候是li-(ri-1)
for (int i=k;i<=n-k;i++){
//寻找第一个长的k最大和的区间
max0=max(max0,nn[i]-nn[i-k]);
//寻找第二个与第一个最大的和
max1=max(max1,max0+nn[i+k]-nn[i]);
}
cout << max1 << endl;
}
return 0;
}
五、国王游戏
链接:https://ac.nowcoder.com/acm/contest/20960/1048
题目描述:
恰逢 H 国国庆,国王邀请 n 位大臣来玩一个有奖游戏。首先,他让每个大臣在左、右手上面分别写下一个整数,国王自己也在左、右手上各写一个整数。然后,让这 n 位大臣排成一排,国王站在队伍的最前面。排好队后,所有的大臣都会获得国王奖赏的若干金币,每位大臣获得的金币数分别是:排在该大臣前面的所有人的左手上的数的乘积除以他自己右手上的数,然后向下取整得到的结果。
国王不希望某一个大臣获得特别多的奖赏,所以他想请你帮他重新安排一下队伍的顺序,使得获得奖赏最多的大臣,所获奖赏尽可能的少。注意,国王的位置始终在队伍的最前面。
输入描述:
第一行包含一个整数 n ,表示大臣的人数。
第二行包含两个整数 a 和 b ,之间用一个空格隔开,分别表示国王左手和右手上的整数。
接下来 n 行,每行包含两个整数 a 和 b ,之间用一个空格隔开,分别表示每个大臣左手和右手上的整数。
输出描述:
一个整数,表示重新排列后的队伍中获奖赏最多的大臣所获得的金币数。
示例1
输入
3
1 1
2 3
7 4
4 6
输出
2
题解:
由于涉及到高精度的问题,用例通过率为50%,等自己能力提高后再改一改
#include <iostream>
#include <algorithm>
using namespace std;
int n;
struct hands
{
int left;
int right;
int product;
}aa[10005];
long long int result[10005];
int cmp(const hands &, const hands &);
//定义排序规则
int cmp(const hands &s1, const hands &s2)
{
//从小排序
return s1.product < s2.product;
}
int main()
{
cin >> n;
for (int i=0;i<n+1;i++){
cin >> aa[i].left >> aa[i].right;
aa[i].product=aa[i].right*aa[i].left;
}
// for (int i=0;i<n+1;i++){
// cout << aa[i].right << endl;
// }
//保持国王在最前面
sort(aa+1,aa+n+1,cmp);
result[0]=aa[0].left;
for (int i=1;i<n;i++){
result[i]=result[i-1]*aa[i].left;
}
cout << (long long int)(result[n-1]/aa[n].right) << endl;
return 0;
}
六、校门外的树
链接:https://ac.nowcoder.com/acm/contest/20960/1017
题目描述:
某校大门外长度为L的马路上有一排树,每两棵相邻的树之间的间隔都是1米。我们可以把马路看成一个数轴,马路的一端在数轴0的位置,另一端在L的位置;数轴上的每个整数点,即0,1,2,……,L,都种有一棵树。
由于马路上有一些区域要用来建地铁。这些区域用它们在数轴上的起始点和终止点表示。已知任一区域的起始点和终止点的坐标都是整数,区域之间可能有重合的部分。现在要把这些区域中的树(包括区域端点处的两棵树)移走。你的任务是计算将这些树都移走后,马路上还有多少棵树。
输入描述:
第一行有两个整数:L(1 <= L <= 10000)和 M(1 <= M <= 100),L代表马路的长度,M代表区域的数目,L和M之间用一个空格隔开。接下来的M行每行包含两个不同的整数,用一个空格隔开,表示一个区域的起始点和终止点的坐标。
输出描述:
包括一行,这一行只包含一个整数,表示马路上剩余的树的数目。
示例1
输入
500 3
150 300
100 200
470 471
输出
298
题解:
#include <iostream>
#include <cstring>
using namespace std;
int m,l;
int delt[10005];
int main()
{
cin >> l >> m;
int left,right;
//初始化
memset(delt,0,l);
while(m--){
cin >> left >> right;
delt[left]++;
delt[right+1]--;
}
//注意题目说是包括两个端点的,也就是说如果L=1,就意味着有两个树,因此实际树木数量是L+1
int count=0;
if (delt[0]==0){
count++;
}
for (int i=1;i<l+1;i++){
delt[i]+=delt[i-1];
if (delt[i]==0){
count++;
}
}
cout << count << endl;
return 0;
}
七、字符串的展开
题目描述:
链接:https://ac.nowcoder.com/acm/contest/20960/1001
在初赛普及组的“阅读程序写结果”的问题中,我们曾给出一个字符串展开的例子:如果在输入的字符串中,含有类似于“d-h”或“4-8”的子串,我们就把它当作一种简写,输出时,用连续递增的字母或数字串替代其中的减号,即,将上面两个子串分别输出为“defgh”和“45678”。在本题中,我们通过增加一些参数的设置,使字符串的展开更为灵活。具体约定如下:
(1)遇到下面的情况需要做字符串的展开:在输入的字符串中,出现了减号“-”,减号两侧同为小写字母或同为数字,且按照ASCII码的顺序,减号右边的字符严格大于左边的字符。
(2)参数 p1p_1p1:展开方式。p1=1p_1=1p1=1 时,对于字母子串,填充小写字母;p1=2p_1=2p1=2 时,对于字母子串,填充大写字母。这两种情况下数字子串的填充方式相同。p1=3p_1=3p1=3时,不论是字母子串还是数字子串,都用与要填充的字母个数相同的星号“*”来填充。
(3)参数 p2p_2p2:填充字符的重复个数。p2=kp_2=kp2=k 表示同一个字符要连续填充 kkk 个。例如,当 p2=3p_2=3p2=3 时,子串“d-h”应扩展为“deeefffgggh”。减号两侧的字符不变。
(4)参数 p3p_3p3:是否改为逆序:p3=1p_3=1p3=1 表示维持原有顺序,p3=2p_3=2p3=2 表示采用逆序输出,注意这时仍然不包括减号两端的字符。例如当 p1=1、p2=2、p3=2p_1=1、p_2=2、p_3=2p1=1、p2=2、p3=2 时,子串“d-h”应扩展为“dggffeeh”。
(5)如果减号右边的字符恰好是左边字符的后继,只删除中间的减号,例如:“d-e”应输出为“de”,“3-4”应输出为“34”。如果减号右边的字符按照ASCII码的顺序小于或等于左边字符,输出时,要保留中间的减号,例如:“d-d”应输出为“d-d”,“3-1”应输出为“3-1”。
输入描述:
第 1 行为用空格隔开的 3 个正整数,依次表示参数 p1,p2,p3p_1,p_2,p_3p1,p2,p3。
第 2 行为一行字符串,仅由数字、小写字母和减号“-”组成。行首和行末均无空格。
输出描述:
输出一行,为展开后的字符串。
示例1
输入
1 2 1
abcs-w1234-9s-4zz
输出
abcsttuuvvw1234556677889s-4zz
示例2
输入
2 3 2
a-d-d
输出
aCCCBBBd-d
示例3
输入
3 4 2
di-jkstra2-6
输出
dijkstra2************6
题解:
考察的就是条件判断,字符串的ascii转换
并没有全通过,通过率为60%,可能问题在于最后都为小写字母的情况时候考虑的不太全,可能都为大写字母的情况也有,就是内循环里最后的else的部分,有空再考虑
#include<iostream>
#include<string>
#include<algorithm>
using namespace std;
bool IsNumber(string);
bool IsLower(string);
bool IsUpper(string);
//这边参数不要写为string类型,因为传进去的是单个字符
bool IsNumber(char c)
{
if ('0'<=c && c<='9'){
return true;
}
return false;
}
bool IsLower(char c){
if ('a'<=c && c<='z'){
return true;
}
return false;
}
bool IsUpper(char c){
if ('A'<=c && c<='Z'){
return true;
}
return false;
}
int main()
{
string s;
int p1,p2,p3;
cin >> p1 >> p2 >> p3;
cin >> s;
// for (int i=0;i<s.length();i++){
// cout << s[i] << endl;
// }
string result="";
for (int i=0;i<s.length();i++){
if (s[i]=='-'){
if (s[i-1]=='-' || s[i+1]=='-'){
result+=s[i];
continue;
}
else if(i==0){
result+=s[i];
continue;
}
else if(i==s.length()-1){
result+=s[i];
continue;
}
else if(s[i-1]==s[i+1]){
result+=s[i];
continue;
}
string tmp="";
if (IsNumber(s[i+1]) && IsNumber(s[i-1])){
//如果这个循环成立就说明右边的字符大于左边的
for (int j=s[i-1]-'0'+1;j<s[i+1]-'0';j++){
for (int k=0;k<p2;k++){
if (p1==3){
tmp+='*';
}
else{
tmp+=j+'0';
}
}
}
if (p3==2){
reverse(tmp.begin(),tmp.end());
}
}
else if(IsLower(s[i-1]) && IsLower(s[i+1])){
//如果这个循环成立就说明右边的字符大于左边的
for (int j=s[i-1]-'a'+1;j<s[i+1]-'a';j++){
for (int k=0;k<p2;k++){
if (p1==3){
tmp+='*';
}
else if (p1==1){
tmp+=j+'a';
}
else{
tmp+=j+'A';
}
}
}
if (p3==2){
reverse(tmp.begin(),tmp.end());
}
}
else{
tmp=s[i];
}
result+=tmp;
}
else{
result+=s[i];
}
}
cout << result << endl;
return 0;
}