by wjl
个人基本是在学习c
文章目录
2021.1.23
某大学ACM实验室寒假新生培训Day1
day1涉及到高精度和贪心算法
计算机思维如何培养?
计算机思维实际上是一种解决问题的方式,它包括四部分:
- 分解
- 模式
- 抽象
- 算法
分解:指将一个复杂的问题拆分成多个简单问题
比如:99×9=(100-1)×9=900-9=891,就是利用了一种简单的方式解决复杂的算术题,就是分解。
模式:观察数据的趋势和规律,识别出它是哪一类问题,找出各个部分之间的异同。
比如:学习认识车辆的时候,会根据是否有四个轮子、是否在马路上跑来判断认识事物。再比如:前面旅游的问题,对于其中的一个小问题路径规划就可以用图论的最短路径理论解决。
抽象:去掉次要的非本质的部分,抽出共同的本质属性。识别模式形成背后的一般规律。
抽象的重点在于区分好复杂度,明辨重要和不重要的信息。比如:我们需要画出整个城市的地铁路线图,这时就会忽略不重要的信息,如路线的产短距离,着重突出需要关注的信息,如几号线和每条线之间换乘的站点等。
算法:未解决某一类问题撰写一系列详细步骤,针对这些类似的问题提供逐步解决的方案
比如:为了不做无用功,我们需要将之前已经识别处理的问题变成一种通用的模式,找出算法之后,并不等于解决了问题,还需要根据实际问题和场景对算法进行适应性调整——通过优化已有问题的解决方案来针对性提高。
ACM国际大学生程度设计竞赛
竞赛参赛队伍往往由三名在校大学生组成,他们需要在规定的五个小时内解决八个或更多(13个题左右)的复杂实际编程问题,通过比赛能够充分展示大学生创新能力、团队精神和在压力下编写程序、分析和解决问题能力。
19年银川约400支队伍参赛
20年小米线下邀请赛,非出线
年度赛事:
- 国家级
- ACM国际大学生程序设计竞赛(ICPC)
- 中国大学生程序设计竞赛(CCPC)
- 全国高校计算机大赛——团体程序设计天梯赛
- 省级
- 河北省ACM大学生程序设计竞赛
- CCPC(河北赛区)
- 企业
- Google Codejam,百度之星,阿里巴巴程序设计大赛,字节跳动……
ACM的益处:
学习:玩ACM,C程序设计,数据结构,算法,离散数学等等这些对于大多数同学有难度的课程直接躺过,拿高分也很容易。
工作:据非官方统计,在ACM区域赛上拿到优异成绩的同学,本科毕业数十万年薪不是不可能的,但归根结底还是要自己努力付出,才会有收获。
保研/考研:保研/考研的面试、笔试、上机,对于ACMer来说,小儿科。学好算法,对以后的深造有极大的帮助,研究生导师都很喜欢算法厉害的学生。
交友:玩ACM可以结实各个高校的朋友,精英。
阅历:每年的区域赛都在全国各地举办
问题C:高精度A+B
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int main()
{
char a1[100],b1[100];
int a[100],b[100],c[100],lena,lenb,lenc,i,x;
memset(a,0,sizeof(a));
memset(b,0,sizeof(b));
memset(c,0,sizeof(c));
gets(a1);
gets(b1);
lena=strlen(a1);
lenb=strlen(b1);
for(i=0;i<=lena-1;i++)
a[lena-i]=a1[i]-48;
for(i=0;i<=lenb-1;i++)
b[lenb-i]=b1[i]-48;
lenc=1;
x=0;
while(lenc<=lena||lenc<=lenb){
c[lenc]=a[lenc]+b[lenc]+x;
x=c[lenc]/10;
c[lenc]%=10;
lenc++;
}
c[lenc]=x;
if(c[lenc]==0)
lenc--;
for(i=lenc;i>=1;i--)
cout<<c[i];
cout<<endl;
return 0;
}
优化后
#include <iostream>
#include <vector>
using namespace std;
typedef vector<int> vint;
vint a, b;
vint add(vint a, vint b){
vint c;
int t = 0;
for (int i = 0; i <= a.size()-1 || i <= b.size()-1; i++){
if(i <= a.size()-1)t += a[i];
if(i <= b.size()-1)t += b[i];
c.push_back(t%10);
t /= 10;
}
if(t)c.push_back(t);
return c;
}
int main()
{
string A,B;
cin >> A >> B;
for (int i = A.size()-1; i >= 0; i --)a.push_back(A[i]-'0');
for (int i = B.size()-1; i >= 0; i --)b.push_back(B[i]-'0');
vint c = add(a, b);
for (int i = c.size()-1; i >= 0; i --)cout<<c[i];
return 0;
}
2021.1.24
某大学ACM实验室寒假新生培训Day2
C++常用STL讲解
C++与C语言对比
C语言版本
#include<stdio.h>
int main()
{
int a,b;
scanf("%d%d",&a,&b);
printf("%d",a+b);
return 0;
}
C++版本
#include<bits/stdc++.h>
using namespace std;
int main()
{
int a,b;
cin>>a>>b;
cout<<a+b;
return 0;
}
C++也可以兼容C语言
#include<bits/stdc++.h>
using namespace std;
int main()
{
int a,b;
scanf("%d%d",&a,&b);
printf("%d",a+b);
return 0;
}
C++中的引用
引用是C++新增的内容,就如同C语言的指针一样重要,但它比指针更加方便和易用,有时候甚至是不可或缺的。
同指针一样,引用能够减少数据的拷贝,提高数据的传递效率。
#include<bits/stdc++.h>
using namespace std;
void swap2(int &a,int &b)
{
int c=a;
a=b;
b=c;
}
int main()
{
int a,b;
cin>>a>>b;
swap2(a,b);
cout<<a<<" "<<b;
return 0;
}
关于STL(标准模板库)
Standard Template Library,缩写为STL
STL是一个C++软件库,里面包括算法、容器、函数、迭代器。
字符串(string)
string是STL中的一个重要部分,主要用于字符串处理。可以使用输入输出流方式直接进行string读入输出,类似于C语言中的字符数组,由于C++的算法库对string类也有着很好的支持,大多时候字符串处理的问题使用string要比字符数组更加方便。
创建string类型变量
string s;
直接创建一个空的(大小为0)的string类型变量s
string s="char";
创建string是直接用字符串内容对其赋值,注意字符串要用双引号
string s(int n,char c);
创建一个string,由n个c组成,注意c是字符型要用单引号
读入string
cin>>s;
durus,遇到空格或回车停止,无论原先s是什么内容都会被新读入的数据替代
getline(cin,s);
读入s,空格也会同样读入,直到回车才会停止
输出string
cout<<s;
将s全部输出到一行(不带回车)
string中的函数:
赋值运算符:**=将后面的字符串赋值给前面的字符串 o(n)
比较运算符:== != < <= > >=比较的两个字符串的字典序大小 o(n)
连接运算符:+ +=**将一个运算符加到另一个运算符后面 o(n)
s[index]
返回字符串s中下标为index的字符,string中下标也是从0开始o(1)
s.substr(p,n)
返回从s的下标p开始的n个字符组成的字符串,如果n省略就取到底o(n)
s.length()
返回字符串的长度o(1)
s.empty()
判断s是否为空,空返回1,不空返回0 o(1)
s.erase(p0,len)
删除s中从p0开始的len个字符,如果len省略就删到底 o(n)
s.erase(s.begin()+i)
删除下标为i个字符 o(n)
s1.insert(p0,s2,pos,len)
后面两个参数截取s2,可以省略 o(n)
s.insert(p0,n,c)
在p0处插入n个字符c o(n)
s.replace(p0,len0,s2,pos,len)
删除p0开始的len0个字符,然后在p0处插入字符串s2中从pos开始的len个字符,后两个参数可以省略 o(n)
s1.find(s2,pos)
从前往后,查找成功时返回第一次出现的下标,失败时返回string::npos的值(-1) 0(n×m)
s1.rfind(s2,pos)
从pos开始从后往前查找字符串s2中字符串后边第一次出现的下标0 (n×m)
#include<bits/stdc++.h>
using namespace std;
int main()
{
string s(3,'6'),ss;
ss=s;
s=s+ss;
cout<<s<<' '<<ss;
return 0;
}
例题:6翻了
一、描述
“666”是一种网络用语,大概是表示某人很厉害、我们很佩服的意思。最近又衍生出另一个数字“9”,意思是“6翻了”,实在太厉害的意思。如果你以为这就是厉害的最高境界,那就错啦 —— 目前的最高境界是数字“27”,因为这是 3 个 “9”!
本题就请你编写程序,将那些过时的、只会用一连串“6666……6”表达仰慕的句子,翻译成最新的高级表达。
二、输入格式
输入在一行中给出一句话,即一个非空字符串,由不超过 1000 个英文字母、数字和空格组成,以回车结束。
三、输出格式
从左到右扫描输入的句子:如果句子中有超过 3 个连续的 6,则将这串连续的 6 替换成 9;但如果有超过 9 个连续的 6,则将这串连续的 6 替换成 27。其他内容不受影响,原样输出。
四、输入样例
it is so 666 really 6666 what else can I say 6666666666
1
五、输出样例
it is so 666 really 9 what else can I say 27
#include<bits/stdc++.h>
using namespace std;
int main()
{
string str;
getline(cin,str);
for(int i=0;i<str.length();i++)
{
if(str[i]=='6')
{
int j=1;//j代表连续6的个数
while(i+j<str.lengeh()&&str[i+j]=='6')j++;
if(j>9)str.replace(i,j,"27");
else if(j>3)str.replace(i,j,"9");
}
}
cout<<str<<endl;
}
vector
vector容器是STL中最常用的容器之一,它和array容器非常类似,都可以看做是对C++普通数组的“升级版”。不同之处在于,array实现的是静态数组(容量固定的数组),而vector实现的是一个动态数组,即可以进行元素的插入和删除,在此过程中,vector会动态调整所占用的内存空间,整个过程无需人工干预。
C++ vector容器
vectorv 创建动态数组v,后面可以加{}或()进行初始化
type v[index]
获取v中第 index 个元素 O(1)
v.push_back(type item)
向v后⾯添加⼀个元素item O(1) (item的类型要和v是相同类型)
v.pop_back()
删除 v 最后⼀个元素 O(1)
v.size()
获取 v 中元素个数,返回size_type类型 O(1)
v.resize(int n)
把 v 的⻓度设定为 n 个元素 O(n)
v.empty()
判断 v 是否为空,空返回1,不空返回0,O(1)
v.clear()
清空 v 中的元素 O(size)
v.insert(iterator it,type x)
向迭代器it指向元素前增加一个元素x,O(n)
v.erase(iterator it)
删除向量中迭代器指向元素,O(n)
v.front()
返回首元素的引用O(1)
v.back()
返回尾元素的引用O(1)
v.begin()
返回首迭代器,指向第一个元素O(1)
v.end()
返回尾迭代器,指向向量最后一个元素的下一个位置O(1)
vector的创建
#include <bits/stdc++.h>
using namespace std;
int main()
{
vector<int>v1;
//创建一个存int类型的动态数组,int可以改成其它类型
vector<double>v2{1,1,2,3,5,8};
//创建一个存double类型的动态数组,长度为6,1 1 2 3 5 8分别存在v[0]~v[5]
vector<long long>v3(20);
//创建一个存long long类型的动态数组,长度为20,v[0]~v[19]默认为0
vector<string>v4(20,"mdltxdy");
//创建一个存string类型的动态数组,长度为20,存的都是"zzuacm"
vector<int>v5[3];
//相当于存int的二维数组,一共3行,每行的列可变
vector<vector<int> >v5{{1,2},{1},{1,2,3}};
//存int的二维数组,行和列都可变,初始状态
return 0;
}
vector基本操作函数
int main()
{
vector<int>v;
for(int i=1;i<=5;i++)
v.push_back(i);//向动态数组中插入1~5
cout<<v.size()<<endl;//输出数组的大小,有几个值
for(int i=0;i<5;i++)
cout<<v[i]<<" ";//输出v[0]~v[4],也就是1~5
cout<<endl;
v.clear();//将v清空,此时size为0
v.resize(10);//为v重新开辟大小为10的空间,初始为0
for(int i=0;i<v.size();i++)
cout<<v[i]<<" ";//遍历每一个元素
while(!v.empty())//当v还不空的话,去掉v的最后一个元素,等同于v.clear();
v.pop_back();
return 0;
}
vector插入、删除、遍历
int main()
{
vector<int>v{0,1,2,3,4};
v.erase(v.begin()+3);//删除v[3],v变为{0,1,2,4}
v.insert(v.begin()+3,666);//在v[3]前加上666,v变成{0,1,2,666,4}
v.front()=10;//将v[0]改成10,等同于v[0]=10;
v.back()=20;//将v[4]改成20等同于v[v.size()-1]=20;
for(int i=0;i<v.size();i++)
cout<<v[i]<<" ";//使用下标访问的方法遍历v
cout<<endl;
for(auto i=v.begin();i!=v.end();i++)
cout<<*i<<" ";//使用迭代器,从v.begin()到v.end()-1
cout<<endl;
for(auto i:v)//使用C++新特性循环遍历v,如果需要改变i的值,还需要在前面加上&
cout<<i<<" ";
cout<<endl;
return 0;
}
关于迭代器(iterator)
vector<int>::iterator it=v.begin();
auto it=v.begin();
两者等价
在这⾥ it 类似于⼀个指针,指向 v 的第⼀个元素
it 等价于 &v[0]
*it
等价于 v[0]
it 也可以进⾏加减操作,例如 it + 3 指向第四个元素
it++ 后it指向的就是v的第二个元素(v[1])了
队列(queue)
queue只能在容器的末尾添加新元素,只能从头部移除元素。
C++ STL–queue的使用方法
创建方法
queue<type>q;
建立一个存放数据类型为type的队列q
使用方法
q.push(item);
在 q 的最后添加⼀个type类型元素item O(1)
q.pop();
使 q 最前⾯的元素出队 O(1)
q.front();
获取 q 最前⾯的元素 O(1)
q.size();
获取 q 中元素个数 O(1)
q.empty();
判断 q 是否为空,空返回1,不空返回0 O(1)
优先队列(priority_queue)
priority_queue中出队顺序与插⼊顺序⽆关,与数据优先级有关,本质是一个堆
priority_queue的用法
创建方法
priority_queue<Type, Container, Functional>
// Type:数据类型
// Container:存放数据的容器,默认⽤的是 vector
// Functional:元素之间的⽐较⽅法,当type可以比较时后两个可以省略
使用方法
pq.push(item);
在 pq 中添加⼀个元素 O(logn)
pq.top();
获取 pq 最大的元素 O(1)
pq.pop();
使 pq 中最大的元素出队 O(logn)
pq.size();
获取 pq 中元素个数 O(1)
pq.empty();
判断 pq 是否为空 O(1)
样例
#include <bits/stdc++.h>
using namespace std;
int main()
{
priority_queue<int> pq;
// priority_queue<int,vector<int>,greater<int> >pq;
pq.push(1);
pq.push(3);
pq.push(2);
while (!pq.empty())
{
cout << pq.top() << endl;
pq.pop();
}
return 0;
}
priority_queue中存的元素如果是结构体这样无法进行比较的类型,必须要重载运算符<,相当于先使得优先队列中的元素可以进行比较再建立pq,否则直接建优先队列是会报错的.
#include <bits/stdc++.h>
using namespace std;
struct Node{
int x,y;
};
bool operator<(Node a,Node b){
return a.x<b.x;
}
int main(){
priority_queue<Node> pq;
pq.push({1,3});
pq.push({3,2});
pq.push({2,1});
while(!pq.empty())
{
cout<<pq.top().x<<' '<<pq.top().y<<endl;
pq.pop();
}
return 0;
}
例题:洛谷p3378【模板】堆
#include<bits/stdc++.h>
using namespace std;
int main(){
priority_queue<int, vector<int>, greater<int> > pq;
int n, x, y;
cin>>n;
while(n--){
cin>>x;
if(x==1){
cin>>y;
pq.push(y);
}else if(x==2){
cout<<pq.top()<<endl;
}else{
pq.pop();
}
}
return 0;
}
集合(set)
集合(set)是一种包含对象的容器,可以快速地(logn)查询元素是否在已知几集合中。
set 中所有元素是有序地,且只能出现⼀次,因为 set 中元素是有序的,所以存储的元素必须已经定义 过「<」运算符(因此如果想在 set 中存放 struct 的话必须⼿动重载「<」运算符,和优先队列一样)
与set类似的还有
multiset元素有序可以出现多次
unordered_set元素无序只能出现一次
unordered_multiset元素无序可以出现多次
set–常见成员函数及基本用法
建立方法
set<Type>s;
multiset<Type>s;
unorded_set<Type>s;
unorded_multiset<Type>s;
如果Type无法进行比较,还需要和优先队列一样定义<运算符
遍历方法
for(auto i:s)cout<<i<<" ";
//和vector的类似
使用方法
s.insert(item);
在 s 中插⼊⼀个元素 O(logn)
s.size();
获取 s 中元素个数 O(1)
s.empty();
判断 s 是否为空 O(1)
s.clear();
清空 s O(n)
s.find(item);
在 s 中查找⼀个元素并返回其迭代器,找不到的话返回 s.end() O(logn)
s.begin();
返回 s 中最小元素的迭代器,注意set中的迭代器和vector中的迭代器不同,无法直接加上某个数,因此要经常用到–和++O(1)
s.end();
返回 s 中最大元素的迭代器的后一个迭代器 O(1)
s.count(item);
返回 s 中 item 的数量。在set中因为所有元素只能在 s 中出现⼀次,所以返回值只能是 0 或 1,在multiset中会返回存的个数 O(logn)
s.erase(position);
删除 s 中迭代器position对应位置的元素O(logn)
s.erase(item);
删除 s 中对应元素 O(logn)
s.erase(pos1, pos2);
删除 [pos1, pos2) 这个区间的位置的元素 O(logn + pos2 - pos1)
s.lower_bound(item);
返回 s 中第一个大于等于item的元素的迭代器,找不到就返回s.end() O(logn)
s.upper_bound(item);
返回 s 中第一个大于item的元素的迭代器,找不到就返回s.end() O(logn)
映射(map)
map 是照特定顺序存储由 key 和 value 的组合形成的元素的容器, map 中元素按照 key 进⾏排序,每个 key 都是唯⼀的,并对应着一个value,value可以重复
map的底层实现原理与set一样都是红黑树
与map类似的还有unordered_map,区别在于key不是按照顺序排序
C++中map的用法详解
例题
建立方法
map<key, value> mp;
unordered_map<key, value>mp;
遍历方法
for(auto i:mp)
cout<<i.first<<' '<<i.second<<endl;
map的常用函数
mp.size();
获取 mp 中元素个数 O(1)
mp.empty();
判断 mp 是否为空 O(1)
mp.clear();
清空 mp O(1)
mp.begin();
返回 mp 中最小 key 的迭代器,和set一样,只可以用到–和++操作 O(1)
mp.end();
返回 mp 中最大 key 的迭代器的后一个迭代器 O(1)
mp.find(key);
在 mp 中查找⼀个 key 并返回其 iterator,找不到的话返回 s.end() O(logn)
mp.count(key);
在 mp 中查找 key 的数量,因为 map中 key 唯⼀,所以只会返回 0 或 1 O(logn)
mp.erase(key);
在 mp 中删除 key 所在的项,key和value都会被删除 O(logn)
mp.lower_bound(item);
返回 mp 中第一个key大于等于item的迭代器,找不到就返回mp.end() O(logn)
mp.upper_bound(item);
返回 mp 中第一个key大于item的迭代器,找不到就返回mp.end() O(logn)
mp[key];
返回 mp 中 key 对应的 value。如果key 不存在,则返回 value 类型的默认构造器(defaultconstructor)所构造的值,并该键值对插⼊到 mp 中O(logn)
mp[key] = xxx;
如果 mp 中找不到对应的 key 则将键值对 (key: xxx) 插⼊到 mp 中,如果存在 key 则将这个 key 对应的值改变为 xxx O(logn)
unordered的作用
set和map都可以在前面加上unorder使得内部的元素改成不按顺序存储的,其余的功能都不改变,虽然无法顺序存储,但底层原理是hash,可以使得所有的查询、修改、删除操作都变成O(1)复杂度
二进制有序集(bitset)
bitset是一种类似数组的结构,它的每一个元素只能是0或1,每个元素仅用1bit空间。
C++的bitset
建立方法
bitset<n>b;//建立一个可以存n位二进制的有序集
bitset<n>b(unsigned long u);//坐标从后往前计数,高位在前
bitset<n>b(string s);//将由'0'和'1'组成的字符串赋值给
支持所有位操作:| |= & &= << <<= >> >>=
cout<<b;//bitset可以直接用cout输出
b[index];//可以用下标操作符来读或写某个索引位置的二进制位
//比如bitset<10>b(5);等价于bitset<10>b("1010");
//其中cout<<b输出0000001010
//for(int i=0;i<10;i++)cout<<b[i];输出0101000000
使用方法
b.count();
count函数用来求bitset中1的位数
b.size();
size函数用来求bitset的大小
b.any();
any函数检查bitset中是否有1
b.none();
none函数检查bitset中是否没有1
b.all();
all函数检查bitset中是全部为1
foo.flip();
flip函数不指定参数时,将bitset每一位全部取反
foo.set();
set函数不指定参数时,将bitset的每一位全部置为1
foo.reset();
reset函数不传参数时将bitset的每一位全部置为0
string s = foo.to_string();
将bitset转换成string类型
unsigned long a = foo.to_ulong();
将bitset转换成unsigned long类型
unsigned long long b = foo.to_ullong();
将bitset转换成unsigned long long类型
常用函数
max(val1, val2);
返回更⼤的数
min(val1, val2);
返回更⼩的数
swap(type, type);
交换两者的值,可以是两个值也可以是两个STL的容器
排序函数(sort)
sort(first, last, compare)
first:排序起始位置(指针或 iterator)
last:排序终⽌位置(指针或 iterator)
compare:⽐较⽅式,可以省略,省略时默认按升序排序,如果排序的元素没有定义比较运算(如结构体),必须有compare
sort 排序的范围是 [first, last),时间为 O(nlogn)
样例
#include<bits/stdc++.h>
using namespace std;
bool cmp(int a,int b){return a>b;}
int main()
{
int arr[]={3,2,5,1,4};
sort(arr,arr+5);
sort(arr,arr+5,greater<int>());
sort(arr,arr+5,cmp);
sort(arr,arr+5,[](int a,int b){return a>b;});
return 0;
}
例题
去重函数(unique)
unique(first, last);
[first, last)范围内的值必须是一开始就提前排好序的
移除 [first, last) 内连续重复项
去重之后的返回最后一个元素的下一个地址(迭代器)
#include<bits/stdc++.h>
using namespace std;
int main()
{
int arr[]={3,2,2,1,2},n;
sort(arr,arr+5);//需要先排序
n=unique(arr,arr+5)-arr;//n是去重后的元素个数
return 0;
}
二分函数(lower_bound/upper_bound)
lower_bound(first, last, value)
first:查找起始位置(指针或 iterator)
last:查找终⽌位置(指针或 iterator)
value:查找的值
lower_bound 查找的范围是 [first, last),返回的是序列中第⼀个大于等于 value 的元素的位置,时间为 O(logn)
[first, last)范围内的序列必须是提前排好序的,不然会错
如果序列内所有元素都⽐ value ⼩,则返回last·upper_bound(first, last, value)
upper_bound 与 lower_bound 相似,唯⼀不同的地⽅在于upper_bound 查找的是序列中第⼀个⼤于 value 的元素
int main()
{
int arr[]={3,2,5,1,4};
sort(arr,arr+5);//需要先排序
cout << *lower_bound(arr,arr+5,3);//输出数组中第一个大于等于3的值
return 0;
}
next_permutation
next_permutation(first, last)
用于求序列[first,last)元素全排列中一个排序的下一个排序
#include<bits/stdc++.h>
using namespace std;
int main()
{
int a[3] = {0, 1, 2};
do
{
for (int i = 0; i < 3; i++)
cout<<a[i]<<' ';
cout<<endl;
}
while (next_permutation(a, a + 3));
return 0;
}