本文共 13735 字,大约阅读时间需要 45 分钟。
本文将提及:
如果本文对你有帮助,记得点赞!
单个字符的处理比数字快,于是我们把数字当成字符来读。(要读很大的数据的时候可以节省很多时间)
templateinline void read(T &x){ x=0;T f=1,ch=getchar(); while(!isdigit(ch)) { if(ch=='-') f=-1; ch=getchar();} while(isdigit(ch)) { x=x*10+ch-'0'; ch=getchar();} x*=f;}
参考了《算法竞赛进阶指南》stl部分和若干博客。
头文件可以用bits/stdc++.h另:输入if else if的时候一定要把整个补全!
ios::sync_with_stdio(false);while(cin>>n,n)//用于判断输入是否为0终止cin>>op>>x;if(op=="A")//用于多操作cout.precision(2);//设置输出精度
#include//对序列进行的一系列基本操作
像sort,reverse之类,都是针对一个左闭右开的区间。
如对一个从a[0]到a[n-1]的数组排序,我们总是这么写: sort(a,a+n);如果是a[1]到a[n]:
sort(a+1,a+n+1)如果是对vector
sort(v.begin(),v.end())即可。对pair排序时默认先对first再对second排序。
sort的第三个参数是一个cmp函数,用于我们对比较进行定义,也可以对小于号进行重载来实现这个功能。常用于你写了一个结构体,要对他们的实体排序的情况。
举个栗子:
int example[N];vector vecample;bool cmp(int a,int b){ return a>b;}sort(example,example+n);sort(vecample.begin(),vecample.end());
再举个栗子:
struct haha{ int x,y;}s[N];bool operator <(const haha &a,const haha &b){ return a.x>b.x||a.x
首先使用这两个函数的数组必须是有序的。
这是两个二分查找的函数,lower_bound()返回第一个大于等于x的最小元素。 upper_bound()返回第一个大于x的元素。 使用嘛就是lower_bound(a,a+n,x)啦,是不是非常简单呢? 但是有人可能会疑惑,都是查找大于等于x的元素,为什么一个是lower一个是upper? 因为upper是用来倒过来算小于等于x的最大元素的。举个栗子:
在有序int数组中查找大于等于x的最小整数的下标int i=lower_bound(a,a+n,x)-a;
再举个栗子:
在有序vector中查找小于x的最大整数
int y=*--upper_bound(a.begin(),a.end(),x);
把指定部分看成一个排列,求出这些元素构成的全排列中,字典序排在下一个的排列,直接在序列上更新。
若不存在返回false 举个栗子:求1~n的全排列for(int i=1;i<=n;a[i]=i,i++);do{ for(int i=1;i<=n;i++) cout< <<' '; cout<
去重,这个离散化很常用了。
和sort一样左闭右开。 计算去重后的个数:int m=unique(a.begin(),a.end())-a.begin();int m=unique(a,a+n)-a;
离散化:
erase(unique(a.begin(),a.end()),a.end());
关于离散化
reverse(a.begin(),a.end());reverse(a,a+n);
#include
只能清零或者-1,或者0x3f,0xcf。
但是注意0x3f,0xcf的值有时候会被修改
,要灵活进行判定。比如floyd算法中:if(d[i][j]>2*INF)。 如果是bool数组
,bool是一字节的可以直接赋值。比如 memset(a,1,sizeof a); 两个变量,前一个是要复制到的目标数组起始地址,后一个是被复制的原数组起始地址,不要写反了。。
举个栗子:char str1[]="sample"; char str2[40]; char str3[40]; strcpy(str2,str1); strcpy(str3,"successful");
strncopy(str2,str1,sizeof(str2));strncpy(str3,str2,5);str3[5]=''\0';
字符串连接
strcpy(str,"this ");strcat(str,"is ");strcat(str,"cat.");
指定位数的字符串连接
strcpy(str1,"examp");strcpy(str2,"leeeee");strncat(str1,str2,2);
=0表示相等。
strcmp(key,buffer)!=0
找出第一个此字母在字符串中的位置。
当然找所有也是可以写的,代码如下:char str[]="this is a sample."key[i]=strchr(str,'s');while(key!=Null){ key[++i]=strchr(key[i-1]+1,'s');}
找出第一个此字符串在原字符串中的位置。
char str[]="this is a sample."key=strstr(str,"sample");
求长度,不包括\0
这些知识只是最初级的字符串处理办法,还有回文串,模式匹配串需要处理。 简单提及一些经典的字符串算法,在以后我的博文中也会提及。#include//声明:bitset<10000> s;//赋值s[0]=1;
bitset相当于二进制压缩,n位bitset做一次位运算复杂度只有n/32.
返回有多少位为1
若s所有位为0,s.none()=true;s.any()=false;
若s有一位为1,s.any()=true;s.none()=false;s.set():把所有位变为1
s.set(k,v):把第k位变成vs.reset():把所有位变为0
s.reset(k):把第k位变成0s.flip():所有位取反
s.flip(k):第k位取反讲一些位运算基本操作:
int Add(int a,int b){ if(b==0) return a;//直到没有进位为止 int sum,carry; sum=a^b;//完成没有进位的加法运算 carry=(a+b)<<1;//进位左移 return Add(sum,cacrry); }
int num(int n){ int t=0; while(n) { t++; n=n&(n-1); } return tl}
这些在最短hamildon路径,二进制枚举,状态压缩,树状数组lowbit函数都有应用。在二进制与倍增那一节会提及。
这些因为比较容易理解,直接上例子,读者可以自行调试比较结果来掌握这些数据结构。
我们先来讲一下pair这也是个常用的结构,不过不需要头文件。
pair是存两个数的结构体的简单写法,有first和second。比较的时候默认先比较first和second,多用于一个区间有l和r,把这两个都存下来。比如贪心法的活动安排问题,经典的区间合并问题。 还可以直接开pair数组,或者用其他数据结构套pair。pairp[N];
vector是个变长的动态数组,数组中可以放数,字符串,pair,结构体等等。
vector不是链表,虽然他也接受随机存取,但是并不是o(1)的,因此我们总是在最后存数或者取数。 声明:vector a;vector b[233];vectorc;
基本函数:
empty和size是所有数据结构共享的。a.size()a.empty() a.clear()a.front()//a[0] *a.begin()a.back()//*--a.end() a[a.size()-1]a.push_back(x)a.pop_back()
迭代器:
迭代器就像STL容器的指针,可以用*操作符解除引用 迭代器之间可以相加减,也可以与整数相加减。 a.begin()返回指向第一个元素的迭代器、 所有的容器都可以视作一个前闭后开的结构,a.end()返回vector最后一个元素再往后的边界。 *a.end()与a[n]一样是越界访问。用迭代器遍历整个vector:
for(vector ::iterator it=a.begin();it!=a.end();it++){ cout<<*it<
迭代器不仅仅在vector可以用,其余数据结构里都可以。
实例:vector模拟邻接表
vector ver[N],edge[N];void add(int x,int y,int z){ ver[x].push_back(y); edge[x].push_back(z);} for(int i=0;i
【我记得蓝桥杯国赛训练营图论都是vector写的
,可以都贴上来】
链式前向星
(数组模拟邻接表),先不写了以后写。 其中包含queue和priority_queue
对于正常的队列,有方法:push,pop,front,back 队列涉及的知识点很多,像等。 对于优先队列,有方法push,pop,top 优先队列可以重载运算符,重载的也是小于号,在讲sort的时候说过了。 priority_queue可以实现大根堆,如果需要实现小根堆,把要插入的元素取相反数插入,取出后再还原。另一种思路priority_queue<int,vector,greater>。priority_queue,greater > pq;//清空while(!pq.empty()) pq.pop();
当优先队列里是pair的时候,如果用定义greater,则first和second都会逆序,如果只需要first逆序,second正序,还是用负号模拟小根堆。
还是要会手写的,因为priority_queue只能做到查找最大值O(1),插入O(logn),删除根O(logn),而手写堆可以做到修改O(logn),删除任意一个。
其中最重要的操作是up()和down()。 【以后做到题目再补充代码吧】另外,优先队列也能实现。
。 单调栈和单调队列O(n) 一般先想暴力,如果具有符合单调栈或者单调队列的一种单调性,则尝试思考这两种数据结构。单调栈用的是stack,如果不单调就把前面的弹出去。
应用场景有个往长方形上贴广告面积(或者只算块数)的裸题
int n=hight.size(); vector stk; for(int i=0;i=heights[i]) stk.pop(); if(stk.empty()) left.push_back(-1); else left.push_back(stk.top()); stk.pop(); stk.push(i); } while(stk.size()) stk.pop(); for(int i=n-1;i>=0;i++)//right不能用pushback会发生翻转问题 { while(stk.size()&&heights[stk.top()]>=heights[i]) stk.pop(); if(stk.empty()) right[i]=n; else right[i]=stk.top(); stk.push(i); } int res=0; for(int i=0;i
接雨水:按层计算。重点是,栈里面还没完全空的时候,加上一个细长的长方形的面积
int trap(vector &height) { int res=0; stack stk; for(int i=0;i
单调队列其实就是个可以pop_front的单调栈,我们利用deque实现。
应用场景:。 滑动窗口裸题:vector res; deque q; for(int i=0;iq.front()) q.pop_front(); while(q.size()&&nums[q.back()]<=nums[i]) q.pop_back();//其实也就像一个队头会出队的单调栈 q.push_back(i); if(i>=k-1) res.push_back(nums[q.front()]);//满了就开始push_back了 } return res;
环形子数组最大和:维护的是前缀和一段的最值。
另外:看到环把他扯成2n长度的线。int n=A.size(); for(int i=0;i
双端队列deque是一个支持在两端高效率插入删除的vector。可以随机索引访问。也可以运用
begin,end, front,back, push_front,push_back, pop_front,pop_back来对首尾进行高效率操作。操作: push/pop/top
运用: 。set最重要的作用就是去重了
包含set(有序去重),multiset(有序多重),unordered_set(无序去重) set和multiset需要定义小于号。 在set中,迭代器遍历的顺序是从小到大的,s.begin()是指向最小元素的迭代器。–s.end()是指向最大元素的迭代器。 s.insert(x) s.find(x): 找到第一个等于x的元素,返回迭代器。如果没有则返回s.end() s.lower_bound(x)/s.upper_bound(x) s.count(x) s.erase(it) 删除迭代器指向的元素 s.erase(x) 删除所有等于x的元素map的内部基于平衡树实现。
平衡树BST就是任何子树的左右子树高度相差最多为1。 让h维持在logn左右,时间复杂度比较小。 有时候BST也要手写,比如动态维护有多少个>=某个数的数,请自行思考。 有以下几种:举个栗子:用map统计字符串出现的次数
maph;char str[25];for(int i=1;i<=n;i++){ cin>>str; h[str]++;} for(int i=1;i<=m;i++){ cin>>str; if(h.find(str)==h.end()) puts("0"); else cout<
map和unordered_map都是做哈希操作的。
哈希是一种映射,把很多数映射到比较少的数上,就能节省时间空间了0v0 但是这不可避免会发生冲突,这时候有两种方式解决冲突:int h[N];int i=(x%N+N)%N;while(h[i]!=Null&&h[i]!=x){ i++; if(i==N)//找到末尾了 i=0; } return i;
拉链:
int h[N],e[N],ne[N],idx;//数组模拟邻接表void insert(int x){ int k=(x%N+N)%N; e[idx]=x; ne[idx]=h[k]; h[k]=idx++;} bool find(int x){ int k=(x%N+N)%N; for(int i=h[k];i!=-1;i=ne[i]) { if(e[i]==x) return true; } return false;}
上面是哈希表的实现方式,实际做题的时候我们用unordered_map就可以解决。
定义、查询和遍历操作#includeunordered_map //key,valuehash["hello"]++;//查询 if(hash.count("hello")!=0)if(hash.find("hello"!=hash.end()))//遍历 for(unordered_map ::iterator it=hash.begin();it!=hash.end();it++)
如果是一个自定义的结构体(这里用了Acwing yxc博文的代码和讲解):
(1) 哈希函数,需要实现一个class重载operator(), 就是你自定义的类型代表哪个数(size_t)?写一个这样的函数。(2) 重载等于号。
代码:#include#include #include using namespace std;class Myclass{ public: int first; vector second; // 重载等号,判断两个Myclass类型的变量是否相等 bool operator== (const Myclass &other) const { return first == other.first && second == other.second; }};// 实现Myclass类的hash函数namespace std{ template <> struct hash { size_t operator()(const Myclass &k) const { int h = k.first; for (auto x : k.second) { h ^= x; } return h; } };}int main(){ unordered_map S; Myclass a = { 2, { 3, 4} }; Myclass b = { 3, { 1, 2, 3, 4} }; S[a] = 2.5; S[b] = 3.123; cout << S[a] << ' ' << S[b] << endl; return 0;}作者:yxc链接:https://www.acwing.com/blog/content/9/来源:AcWing
hash一般在题目数字比较庞大爆ull
的时候使用。或者用来降低复杂度,避免重复遍历
,就像查表一样O(1)插入和查询。
此外还有很多不在头文件的数据结构,在数据结构的博文中会进行详细的扩充。
忘记 输入,==,忘记初始化,浮点数精度,一些语句放循环内还是循环外,数组开小了
一些边缘数据一定要记得考虑!0和1和2之类的!
前缀和用于求一个数组中,从l到r这一段的和。
动态维护前缀和请转到线段树和树状数组。前缀最值
维护一个前缀的最值,实时比较实时操作。 例如下面这题:LC155//min stack void push(int x) { stk.push(x); if(stk_min.empty()) stk_min.push(x); else stk_min.push(min(x,stk_min.top())); } void pop() { stk.pop(); stk_min.pop(); } int top() { return stk.top(); } int getMin() { return stk_min.top(); }
双指针在leetcode里有很多题目都很好。
先想暴力怎么做,看看双指针能否优化(一般会具有某种单调性,让i和j之间相互约束从而减少复杂度)。归并排序就是一个典型的双指针算法,这是用于对比两个数组的。值得一提的是,如果要放在原数组中建议倒序遍历。
unique函数的实现也是双指针算法,一个指针指向遍历到哪里,另一个指向数字放到哪里。 在排好序的数组中找出两数之和等于k,可以利用单调性,若是i1>i2必定有j1<j2,所以一个正序一个倒序遍历即可。下面是两题比较有思考价值的:
最小覆盖子串问题 两个指针就像两个边界,维护一个窗口,开hash表统计T里面字母出现次数。 这里有个技巧,我们把目标的数值作为hash值,就能统一用hash[i]是否为0表示我们是否达到了要求。 如果多余了,前指针就往后指,达到目标的时候与res比较看是否更短,更新最小值。unordered_maphash;for(auto c:t) hash[c]++;//我们需要的字母都变成1 int cnt=hash.size();string res;for(int i=0,j=0;i i-j+1) res=substr(j,i-j+1); }
括号匹配问题
只要涉及括号的匹配,就会有个catlan数的性质出现。 设左括号为1,有括号为-1. 括号序列合法等价于所有前缀和大于等于0且总和等于0. 分cnt与零的关系分类讨论。 最后还要反过来做一遍。于是我们可以写一个函数做,这样不用写两遍,直接reverse原序列再做。 代码:int work(string s){ int res=0; for(int i=0,start=0,cnt=0;i
高精度加法
思路:先把a和b倒序一下,如果哪个长就继续做和0的加法。 add中保存好进位,往vector中push当前位。 最后别忘了倒序回来。#include#include #include #include using namespace std;vector v;string a,b;void add(char a,char b,int &next){ int sum=(a-'0')+b-'0'+next; int local=sum%10; v.push_back(local); next=sum/10;}int main(){ cin>>a>>b; reverse(a.begin(),a.end()); reverse(b.begin(),b.end()); int i=0; int next=0; while(i
高精度乘法
思路:保存进位,push当前位。如果最后还有进位也要push进去。#include#include #include #include using namespace std;vector v;string a;int b;void multi(string a,int b){ int t=0; for(int i=0;i >a>>b; reverse(a.begin(),a.end()); multi(a,b); reverse(v.begin(),v.end()); for(int i=0;i
高精度除法
思路:和列除法式子一样,余数乘10进行下一轮。 另:最后倒过来删除前导零。#include#include #include #include using namespace std;vector v;string a;int b,r=0;void div(string a,int b,int &r){ for(int i=0;i 1&&v.back()==0) { v.pop_back(); } }int main(){ cin>>a>>b; div(a,b,r); reverse(v.begin(),v.end()); for(int i=0;i
这个仍然参考了《算法竞赛进阶指南》0v0
下面要讲的快速幂就是一种倍增技巧的应用。 背包问题中可以用倍增优化。 ST算法实现LCA也是倍增的应用。倍增简而言之就是由于任何整数都可以表示若干个2的幂次和,我们用2的幂次和表示0~2k-1,只需要logK个代表值,大大降低了复杂度。
如果空间太大,线性递推没办法满足时间与空间复杂度要求,也可以用倍增进行优化。 【代码以后补充?】二分幂和快速幂的想法很相似,只不过不用二进制写,这里就不说了。当进行数的幂次运算时,复杂度由O(n)降低为O(logn)。
思路:枚举每一位,如果是1,则res*=a,如果不是1,就将a=a*a提升一位。一般这种题都会越界要记得%mod
代码:int quickpow(int a,int b){ int res=1; while(b) { if(b&1) { res=res*a%mod; } a=a*a%mod; b=b>>1; } return res; }
矩阵快速幂同理,只不过初始化的时候是一个单位矩阵,并且乘的时候用一个函数实现矩阵乘法(矩阵用一个记录维度和内容的结构体存储):
struct matrix { int a[100][100]; int n;};matrix matrix_mul(matrix A, matrix B, int mod) { matrix ret; ret.n = A.n; for (int i = 0; i < ret.n; i++) { for (int j = 0; j < ret.n; j++){ ret.a[i][j] = 0; } } for (int i = 0; i < ret.n; i++) { for (int j = 0; j < ret.n; j++) { for (int k = 0; k < A.n; k++) { ret.a[i][j] = (ret.a[i][j] + A.a[i][k] * B.a[k][j] % mod) % mod; } } } return ret;}matrix unit(int n){ matrix ret; ret.n=n; for(int i=0;i
单纯的矩阵乘法需要判断中间那一维是否能接上:
#includeusing namespace std;struct matrix{ int a[100][100]; int n,m;};matrix matrix_mul(matrix A,matrix B){ matrix ret; ret.n=A.n; ret.m=B.m; for(int i=0;i >A.n>>A.m; for(int i=0;i >A.a[i][j]; } } cin>>B.n>>B.m; for(int i=0;i >B.a[i][j]; } } if(A.m!=B.n) { cout<<"No"<
动态规划中有一个经典题目,如何加括号让矩阵的连乘次数最少。
我们在讲STL的时候已经提到,用erase(unique(v.begin(),v.end()),v.end())这句话进行离散化。那么离散化的应用场景是什么呢?
一些数字,他们有着很大的范围,但是个数很少,并且这些数字大小本身不重要,只看他们之间的大小关系。数量庞大的方块,他们之间有的有颜色,但大多没颜色。
一个超大的迷宫,空白的地方很多。
所以离散化的应用场景大家就自己意会吧。
【写到题目再更】 写太多了,其余寒假放到下篇讲。。。。转载地址:http://uyvx.baihongyu.com/