专题1(时空复杂度,STL,语法糖)

一、时空复杂度


1.时间复杂度:

时间复杂度衡量程序效率的量度

时间复杂度的分类

分类记号
最大(最坏)时间复杂度
O ( f )
最小(最好)时间复杂度
Ω( f )
精确时间复杂度
Θ( f )

默认使用最大时间复杂度,即O( f )。

时间复杂度的计算(化简规则)

1. 常数项化为 1;
2. 各项的系数化为 1;
3. 只保留最高阶的项;
4. 忽略对数的底数,loga n 应化为 log n
T(n)=n\sqrt{3n}+3nO(n\sqrt{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(\sqrt{n}

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(链表):可以在任意位置插入删除,但不可以随机访问

list 不支持随机访问,因此对于 list 的两个迭代器 p1 p2 ,不能用 p1 - p2 计算它们的距离,必须使用 std::distance(p1, p2) ,其时间复杂度是 O ( n )
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 不支持随机访问,因此对于 set 的两个迭代器 p1 p2 ,不能用 p1 - p2 计算它们的距离,必须使用 std::distance(p1, p2) ,其时间复杂度是 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循环

范围 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定义别名

看上去比 typedef 更优美。
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表达式

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≤a^{3})— 表示测试用例的数量。

每个测试用例的描述包括一行,包含一个由三个字符组成的字符串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 ] ]输出交换后的矩阵

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值