一、时空复杂度
1.时间复杂度:
时间复杂度是衡量程序效率的量度
时间复杂度的分类
分类 | 记号 |
最大(最坏)时间复杂度
|
O
(
f
)
|
最小(最好)时间复杂度
|
Ω(
f
)
|
精确时间复杂度
|
Θ(
f
)
|
默认使用最大时间复杂度,即O( f )。
时间复杂度的计算(化简规则)
O(n | |
T
(
n
) = 5
n
log
3
n
|
O
(
n
log
n
)
|
eg:
int func5(int n) {
int i = 0, sum = 0;
while (sum <= n) {
i ++;
sum += i;
}
return sum;
}
该函数时间复杂度为O()
2.空间复杂度
二、STL
1.pair:组合任意两个不同类型的元素(相同元素也行)
变量名 | 内容 |
first | 第一个值 |
second | 第二个值 |
#include<bits/stdc++.h>
using namespace std;
int main(){
pair <char,string> p1={'x',"我爱学习"};
pair <string,string> p2={"芙宁娜","Furina"};
cout<<"p1:";
cout<<p1.first<<" "<<p1.second;
cout<<"\n";
cout<<"p2:";
cout<<p2.first<<" "<<p2.second;
}
2.stack(栈):在栈顶插入删除(先进后出)
top() | 返回栈顶元素 |
empty() | 返回是否为空 |
size() | 返回元素个数 |
push(x) | 栈顶插入元素x |
pop() | 删除栈顶元素 |
3.queue(队列):队尾插入元素,队头删除元素(先进先出)
front() | 返回队头元素 |
back() | 返回队尾元素 |
empty() | 返回是否为空 |
size() | 返回元素个数 |
push(x) | 队尾插入元素x |
pop() | 删除队头元素 |
4.priority_queue(优先队列):自动排序,但是仅可访问最大元素
小根堆:
priority_queue<int, vector<int> ,greater<int> > pq;
top() | 返回最大元素 |
empty() | 范围是否为空 |
size() | 返回元素个数 |
push(x) | 插入元素x |
pop() | 删除最大元素 |
5.vector(动态数组):根据需要自动扩容,也可手动调整
assign(count, value)
|
初始化为
count
个value
|
at(pos)
|
返回第
pos
个元素
|
operator [pos]
|
返回第
pos
个元素
|
front()
|
返回第一个元素
|
back()
|
返回最后一个元素
|
begin()
|
返回头部迭代器
|
end()
|
返回尾部迭代器
|
empty()
|
返回是否为空
|
size()
|
返回元素个数
|
clear()
|
清空
|
push_back(value)
|
在尾部插入
value
|
pop_back()
|
删除尾部元素
|
resize(count)
|
将容量调整为
count
|
ps:begin():指向头部元素 / end():指向尾部元素的后继
6.deque(双端队列):在vector上增加的头部的插入删除
vector中的函数 | |
push_front(value)
|
在头部插入
value
|
pop_front()
|
删除头部元素
|
7.list(链表):可以在任意位置插入删除,但不可以随机访问
assign(count, value)
|
初始化为
count
个
value
|
front()
|
返回第一个元素
|
back()
|
返回最后一个元素
|
begin()
|
返回头部迭代器
|
end()
|
返回尾部迭代器
|
empty()
|
返回是否为空
|
size()
|
返回元素个数
|
clear()
|
清空
|
insert(pos, value)
|
在迭代器
pos
处插入
value
|
erase(pos)
|
删除迭代器
pos
处的元素
|
push_back(value)
|
在尾部插入
value
|
pop_back()
|
删除尾部元素
|
push_front(value)
|
在头部插入
value
|
pop_front()
|
删除头部元素
|
resize(count)
|
将容量调整为
count
|
8.set(集合):插入的各种元素都只保留其一,并自动升序
STL也提供了multiset(多重集):相同的元素允许存在多个,其余功能均与set相同
unordered_set功能与set一致时间复杂度有所区别:精心设计的输入数据会使 unordered_set 始终保持最坏时间复杂度。
函数名
|
功能
|
时间复杂度
|
时间复杂度(
unordered
)
|
begin()
|
返回头部迭代器
|
O
(1)
|
O
(1)
|
end()
|
返回尾部迭代器
|
O
(1)
|
O
(1)
|
empty()
|
返回是否为空
|
O
(1)
|
O
(1)
|
size()
|
返回元素个数
|
O
(1)
|
O
(1)
|
clear()
|
清空
|
O
(
n
)
|
O
(
n
)
|
insert(value)
|
插入一个
value
|
O
(log
n
)
|
平均
O
(1)
,最坏
O
(
n
)
|
erase(value)
|
删除所有
value
|
O
(log
n
)
|
平均
O
(1)
,最坏
O
(
n
)
|
erase(iter)
|
删除迭代器
iter
指向的单个元素
|
O
(log
n
)
|
平均
O
(1)
,最坏
O
(
n
)
|
count(value)
|
返回
value
的个数
|
O
(log
n
)
|
平均
O
(1)
,最坏
O
(
n
)
|
find(value)
|
返回一个
value
的迭代器
|
O
(log
n
)
|
平均
O
(1)
,最坏
O
(
n
)
|
set<int> s;
s.insert(1);
s.insert(2);
s.insert(3);
// s = {1, 2, 3}
s.insert(2);
// s = {1, 2, 3}
s.erase(2);
// s = {1, 3}
for (int value : s) cout << value << ' ';
9.map(映射):相当于[ ]内可以是任何键值的数组
map<T1, T2> 相当于 set<pair<T1, T2>> 。实际上, map 内部存储的就是 pair 。
STL存在unordered_map,功能与map一致,但时间复杂度不同:精心设计的输入数据会使 unordered_map 始终保持最坏时间复杂度。
at(key)
|
返回
key
映射的元素
|
O
(log
n
)
|
平均
O
(1)
,最坏
O
(
n
)
|
operator
[key]
|
返回
key
映射的元素
(如没有则创建)
|
O
(log
n
)
|
平均
O
(1)
,最坏
O
(
n)
|
begin()
|
返回头部迭代器
|
O
(1)
|
O
(1)
|
end()
|
返回尾部迭代器
|
O
(1)
|
O
(1)
|
empty()
|
返回是否为空
|
O
(1)
|
O
(1)
|
size()
|
返回元素个数
|
O
(1)
|
O
(1)
|
clear()
|
清空
|
O
(
n
)
|
O
(
n
)
|
count(key)
|
返回
key
映射的元素个数
|
O
(log
n
)
|
平均
O
(1)
,最坏
O
(
n
)
|
erase(key)
|
删除从
key
出发的映射
|
O
(log
n
)
|
平均
O
(1)
,最坏
O
(
n
)
|
map<char,int> mp;
mp['y']=0;
mp['l']=5;
mp['n']=2;
mp['a']=1;
mp.erase('a');
for (auto el:mp){
cout<<el.first<<" "<<el.second<<endl;
}
10.string(字符串)
成员函数
assign(count, value)
|
初始化为
count
个
value
|
at(pos)
|
返回第
pos
个字符
|
operator[pos]
|
返回第
pos
个字符
|
front()
|
返回第一个字符
|
back()
|
返回最后一个字符
|
c_str()
|
返回 c 风格字符串
|
begin()
|
返回头部迭代器
|
end()
|
返回尾部迭代器
|
empty()
|
返回是否为空
|
size()
|
返回字符串长度
|
clear()
|
清空
|
push_back(ch)
|
在尾部插入
ch
字符
|
pop_back()
|
删除尾部字符
|
append(str)
|
在尾部插入
str
字符串
|
operator += str
|
在尾部插入
str
字符串
|
resize(count)
|
将字符串长度调整为
count
|
substr(pos, count)
|
截取第
pos
个字符开始、长度为
count
的子串
|
substr(pos)
|
截取第
pos
个字符开始到末尾的子串
|
非成员函数
operator str1 + str2
|
拼接字符串
|
operator str1 == str2
|
等于
|
operator str1 < str2
|
小于(按字典序比较)
|
operator str1 > str2
|
大于(按字典序比较)
|
operator str1 <= str2
|
小于等于(按字典序比较)
|
operator str1 >= str2
|
大于等于(按字典序比较)
|
stoi(str)
|
字符串转
int
|
stoll(str)
|
字符串转
long long
|
stoull(str)
|
字符串转
unsigned long long
|
stof(str)
|
字符串转
float
|
stod(str)
|
字符串转
double
|
stold(str)
|
字符串转
long double
|
to_string(value)
|
数字转字符串(支持
int
,
double
等)
|
-
cin,cout 可以直接输入输出 string ;
- scanf无法输入,printf可以输出string的c风格形式
11.sort:给一个序列排序
时间复杂度为O(nlogn)
void sort(Iterator first, Iterator last);
void sort(Iterator first, Iterator last, Compare cmp);
- first:头部元素的迭代器或者指针
- last:尾部元素的后继迭代器或者指针
- cmp:自定义比较函数,用于控制排序的升降(非必要)
bool cmp(int l, int r) { return l > r; } int main() { char a[] = {'f','u','r','i','n','a'}; //sort(a, a + 6, cmp); sort(a, a + 6, std::greater<int>());//两种写法效果相同 for (char el : a) cout << el << ' '; }
12.reverse:反转序列
void reverse(Iterator first, Iterator last);
first与last均同上
int main() {
char a[] = {'f','u','r','i','n','a'};
reverse(a, a + 6);
for (char el : a) cout << el << ' ';
}
13.unique:移除序列中连续重复的元素(将其移动到末尾,并返回指向末尾的迭代器)
Iterator unique(Iterator first, Iterator last);
first,last同上
int main() {
vector<int> vec = {1, 1, 2, 2, 1, 1, 1};
auto pos = std::unique(vec.begin(), vec.end());
vec.erase(pos, vec.end());
for (int el : vec) std::cout << el << ' ';
}
14.lower_bound:在有序序列中二分查找第一个大于等于给定值的元素,并返回它的迭代器
- 时间复杂度为O(nlogn)
- 在使用lower_bound前,序列必须是升序的
- STL也提供了upper_bound,用于二分查找第一个大于给定值的元素,二者用法相同
Iterator lower_bound(Iterator first, Iterator last, T value);
- value:给定值
int main() {
vector<int> vec = {1, 2, 4, 4, 5, 6, 7};
auto it = std::lower_bound(vec.begin(), vec.end(), 4);
// 查找第一个 ≥ 4 的元素
cout << distance(vec.begin(), it);
// 输出它对应的下标
return 0;
}
三、算法糖
1.auto关键字
声明变量时用auto代替类型名,让编译器自动推导其类型
2.范围for循环
int main() {
int a[] = {1, 1, 4, 5, 1, 4};
for (int el : a) std::cout << el << ' ';
return 0;
}
string toUpperCase(std::string s) {
for (auto& ch : s) // 不能漏加「&」,否则变更不会保存到 s 中
ch = toupper(ch);
return s;
}
int main() {
cout << toUpperCase("furina");
return 0;
}
3.使用using定义别名
using TypeB = TypeA;
template <typename T>
using intTo = std::map<int, T>;
int main() {
intTo<int> map1;
intTo<char> map2;
intTo<double> map3;
...
}
ps:使用using可以更方便地定义模板类的别名
4.Lambda表达式
auto funcName = [&](Type1 x1, Type2 x2, ...) -> returnType {
...
};
- 最后的分号不能漏
-
当采用 [&] 时,Lambda 函数可以修改外部的变量,采用 [=] 时则不行
-
-> returnType 可省略
四、解题思路
1.Long Loong
题目:对于一个正整数 X,级别为 X的 龙字符串 是一个长度为 (X+3) 的字符串,由一个 L
、X 次出现的 o
、一个 n
和一个 g
按此顺序排列而成。
给定一个正整数 NN。打印级别为 NN 的龙字符串。
#include<bits/stdc++.h>
using namespace std;
int main(){
int n;
cin>>n;
cout<<"L";
for (int i=0 ;i<n ;i++) cout<<"o";
cout<<"ng";
}
解题思路:c++基础的输入输出
2.YES or YES?
给定一个长度为3的字符串,由大写和小写英文字母组成。检查它是否等于"YES"(不包括引号),其中每个字母都可以是任何大小写。例如,"yES", "Yes", "yes" 都是合法的。
输入
输入的第一行包含一个整数t(1≤t≤)— 表示测试用例的数量。
每个测试用例的描述包括一行,包含一个由三个字符组成的字符串ss。每个ss的字符要么是大写字母,要么是小写字母。
输出
对于每个测试用例,如果s 满足条件,则输出 "YES"(不包括引号),否则输出 "NO"(不包括引号)。
你可以以任何大小写形式输出 "YES" 和 "NO"(例如,字符串 "yES", "yes" 和 "Yes" 都会被识别为正面回应)。
#include<bits/stdc++.h>
using namespace std;
int main(){
string s;
int n;
cin>>n;
for (int i=0 ;i<n ;i++){
int flag=0;
cin>>s;
if (s[0]=='Y' || s[0]=='y'){
if (s[1]=='E' || s[1]=='e'){
if (s[2]=='S' || s[2]=='s'){
flag=1;
}
}
}
if (flag==0) cout<<"NO\n";
else cout<<"YES\n";
}
}
解题思路:输入字符串s后,依次判断各位是否符合要求,并用flag记录对比结果
3.Even? Odd? G
Bessie那惨无人道的二年级老师搞了一个有 N 个正整数 I 的表叫Bessie去判断“奇偶性”(这个词语意思向二年级的学生解释,就是“这个数是单数,还是双数啊?”)。Bessie被那个表的长度深深地震惊到了,竟然跟栋栋的泛做表格一样多道题!!!毕竟她才刚刚学会数数啊。
写一个程序读入N个整数,如果是双数,那么在单立的一行内输出"even",如果是单数则类似地输出"odd".
数据范围:每个正整数不超过 10^60
输入 | 输出 |
2 1024 5931 | even odd |
#include<bits/stdc++.h>
using namespace std;
int main(){
string s;
int n;
cin>>n;
for (int i=0 ;i<n ;i++){
cin>>s;
int l=s.length();
if(s[l-1]%2==0) cout<<"even\n";
else cout<<"odd\n";
}
}
解题思路:因为正整数的数据可能会很大,所以将正整数以字符串的形式输入,并通过判断其最后一位数的奇偶来确定整个正整数的奇偶
4.Problem Generator
Vlad计划下个月举办m轮比赛。每一轮比赛应包含难度等级为'A', 'B', 'C', 'D', 'E', 'F', 和 'G' 的一个问题。
Vlad已经准备了n个问题,其中第i个问题的难度等级为ai。可能这些问题数量不够,所以他可能需要再想出一些问题。
Vlad希望尽可能少地想出问题,因此他请你找出他需要想出的问题的最少数量,以便举办mm轮比赛。
例如,如果m=1,n=10,a= 'BGECDCBDED',那么他需要想出两个问题:一个难度等级为'A',一个难度等级为'F'。
输入
第一行包含一个整数tt (1≤t≤1000) — 测试用例的数量。
每个测试用例的第一行包含两个整数n和m (1≤n≤50, 1≤m≤5) — 银行中问题的数量和即将举办的轮数。
每个测试用例的第二行包含一个长度为n的字符串a,包含从'A'到'G'的问题难度。
输出
对于每个测试用例,输出一个整数 — 需要想出的问题的最少数量,以便举办m轮比赛。
输入 | 输出 |
3 10 1 BGECDCBDED 10 2 BGECDCBDED 9 1 BBCDEFFGG | 2 5 1 |
#include<bits/stdc++.h>
using namespace std;
int main(){
map<char,int> mp;
int t;
cin>>t;
for (int i=0 ;i<t ;i++){
int n,m;
string s;
cin>>n>>m;
cin>>s;
for (char j='A' ;j<='G' ;j++) mp[j]=0;
int l=s.length();
for (int j=0 ;j<l ;j++) mp[s[j]]++;
int ans=0;
for (char j='A' ;j<='G' ;j++){
if (mp[j]<m) ans+=m-mp[j];
}
cout<<ans<<"\n";
}
}
解题思路:通过使用STL提供的map来记录各各难度的题目的数量(在每次循环开始前将其归零),然后经过对比计算求出结果
5.rules
小 A 制定了一些规则,每条规则有一个代号,代号为不超过 10^9的非负整数。
小 A 的国家有 n位居民,每位居民每天会且仅会遵守 1 条规则。小 A 记录了 m 天里每天每位居民遵守的规则代号。
现在小 A 想要考察代号为 kk 的规则是否符合民意,具体考察方法如下:
- 如果在某一天里,有大于等于一半的人遵守了规则 k,那么小 A 认为在这一天规则 k 是符合民意的。
- 如果在大于等于一半的天数里,规则 k 符合民意,那么他会认为规则 k 是正确的。否则,他会认为规则 k 是错误的。
如果小 A 的规则 k 是正确的,请你输出 YES
,否则请你输出 NO
。
Input
第一行三个整数 n,m,k分别表示居民总数、记录的天数和小 A 想要考察的规则的代号。
接下来 mm行,每行 n个整数分别表示每个人分别遵守的规则代号。
Output
一行一个字符串 YES
或 NO
表示小 A 的规则 k 是否是被视作正确的。
输入 | 输出 |
3 2 1 1 1 2 3 1 2 | YES |
3 2 1 9 9 8 1 9 9 | NO |
#include<bits/stdc++.h>
using namespace std;
int main(){
int n,m,num,day;
string k;
cin>>n>>m>>k;
day=0;
for (int i=0 ;i<m ;i++){
string s;
num=0;
for (int j=0 ;j<n ;j++){
cin>>s;
if (s==k) num++;
}
if (num>=(double)n/2) day++;
}
if (day>=(double)m/2) cout<<"YES";
else cout<<"NO";
}
解题思路:
- 因为规则代号可能会很大,所以将其以string类型输入
- 用num记录当天遵守该规则的居民,并用day记录符合民意的天数
- 因为计算机除法计算特性,比较时可以将n与m转换成double类型
6.Many Replacement
问题陈述
给定一个长度为 N 的字符串 S,由小写英文字母组成。
你将对字符串 S执行 Q 次操作。 第 i 次操作 (1≤i≤Q)由一对字符(ci,di) 表示,对应以下操作:
- 将字符串 S 中所有字符 ci 替换为字符 di。
在所有操作完成后,打印字符串 S。
约束条件
- 1≤N≤2×10^5
- S 是一个长度为 N 的字符串,由小写英文字母组成。
- 1≤Q≤2×10^5
- ci和di是小写英文字母 (1≤i≤Q)。
- N 和 Q是整数。
输入
输入通过标准输入以以下格式给出:
N S Q c1 d1 c2 d2 ⋮⋮ cQ dQ |
输出 在所有操作完成后,打印字符串 S。
输入 | 输出 |
7 atcoder 4 r a t e d v a r | recover |
#include<bits/stdc++.h>
using namespace std;
map<char,char> mp;
int main(){
int n,m;
string s;
cin>>n>>s>>m;
for (char i='a' ;i<='z' ;i++) mp[i]=i;
while(m--){
char a,b;
cin>>a>>b;
for (char j='a' ;j<='z' ;j++){
if (mp[j]==a) mp[j]=b;
}
}
for (int i=0 ;i<n ;i++) cout<<mp[s[i]];
}
解题思路:
- 使用map记录各个字母经过操作后的结果,通过这种方式可以节省运行时间,避免超时
- 在进行替换操作时要找到当前替换结果为a的字母,并将它们的替换结果都改成b
7.更好的交换
Description
小 S 有一个奇怪的机关拼图。这个拼图可以看作一个 n 行 n 列的方阵 A,第 i 行第 j 列的位置上有一个正整数 Ai,jAi,j。
与寻常拼图不同的是,这个机关拼图上的数字不能随意移动,必须按照如下规则之一操作:
- 选择拼图上的第 x 行和第 y 行,交换这两行;
- 选择拼图上的第 x 列和第 y 列,交换这两列。
为了复原这个拼图,小 S 将会操作共 m 次,每次操作格式如下:
1 x y
,表示交换第 x 行和第 y 行;0 x y
,表示交换第 x 列和第 y 列;
请你输出复原后的拼图。
Input
第一行,两个正整数 n 和 m,分别表示拼图的行数、列数和总操作次数。
接下来 n 行,每行 n 个正整数 Ai,jAi,j,表示拼图上第 i 行,第 j 列上的数字。
接下来 m 行,每行三个正整数 op,x,y其中 op 表示操作类型 x,y 代表被操作的行号或列号。
Output
输出共 n 行,每行 n 个正整数,表示复原后的拼图。
输入 | 输出 |
4 4 12 32 42 82 53 43 34 98 90 32 42 53 37 17 88 10 0 2 4 1 2 4 0 1 4 1 1 3 | 32 53 42 90 17 10 88 37 32 82 42 12 43 98 34 53 |
#include<bits/stdc++.h>
using namespace std;
int main(){
int n,m;
cin>>n>>m;
int a[n+1][n+1];
for (int i=1 ;i<=n ;i++){
for (int j=1 ;j<=n ;j++) cin>>a[i][j];
}
int x[n+1],y[n+1];
for (int i=1 ;i<=n ;i++) {
x[i]=i;
y[i]=i;
}
int op,u,v,t;
while(m--){
cin>>op>>u>>v;
if (op==1){
t = x[u];
x[u] = x[v];
x[v] = t;
}else {
t = y[u];
y[u] = y[v];
y[v] = t;
}
}
for (int i=1 ;i<=n ;i++){
for (int j=1 ;j<=n ;j++){
cout<<a[x[i]][y[j]];
if (j!=n) cout<<" ";
}cout<<"\n";
}
}
解题思路:大致思路与上一题一致,用二维数组记录矩阵,并通过x[ ]与y[ ]两个数组分别记录行交换与列交换后结果,经过操作后,现在的第i行为原先的第x[i]行,现在的第i列为原先的第y[i]列,最后用a[ x[ i ] ][ y[ j ] ]输出交换后的矩阵