【AcWing算法基础课】第二章 数据结构(部分待更)(3)

简介: 路径压缩:查找时,如果还没有找到目标值的父结点时,将路径上每个点的父结点,在向上寻找过程中更新记录。

九、并查集

9190814cf73d88af65df10717056def0_99d19bd0bb1349e7b25b795601ec28b1.png


核心模板

朴素并查集:

902fa89b0809aa0c3f674f496a4e818d_dddaac15af8643b4928d5ec44d250509.png


路径压缩:查找时,如果还没有找到目标值的父结点时,将路径上每个点的父结点,在向上寻找过程中更新记录。


int p[N];//存储每个点的祖宗结点
//返回x的祖宗结点
int find(int x){
    if(p[x]!=x) p[x]=find(p[x]);
    return p[x];
}
//初始化,假定结点编号是1~n
for(int i=1;i<=n;i++){
    p[i]=i;
}
//合并a和b所在的两个集合
p[find(a)]=find(b);
1


维护size的并查集

//注意size可能与某些内置变量名冲突,故改成了num

int p[N],num[N];

//p[]存储每个结点的祖宗结点,num[]只有祖宗结点有意义,表示祖宗结点所在集合中点的数量

//返回x的祖宗结点

int find(int x){
    if(p[x]!=x) p[x]=find(p[x]);
    return p[x];
}
//初始化,假定结点编号是1~n
for(int i=1;i<=n;i++){
    p[i]=i;
    num[i]=1;
}
//合并a和b所在的两个集合
num[find(b)]+=num[find(a)];
p[find(a)]=find(b);


维护到祖宗结点距离的并查集

int p[N],d[N];
//p[]存储每个结点的祖宗结点,d[x]存储x到p[x]的距离
//返回x的祖宗结点
int find(int x){
    if(p[x]!=x){
        int u=find(p[x]);
        d[x]+=d[p[x]];
        p[x]=u;
    }
    return p[x];
}
//初始化,假定结点编号是1~n
for(int i=1;i<=n;i++){
    p[i]=i;
    d[i]=0;
}
//合并a和b所在的两个集合
p[find(a)]=find(b);
d[find(a)]=distance;//根据具体问题,初始化find(a)的偏移量


题目一

题目链接:836. 合并集合


9.1题目描述

一共有 n 个数,编号是 1∼n,最开始每个数各自在一个集合中。


现在要进行 m 个操作,操作共有两种:


M a b,将编号为 a 和 b 的两个数所在的集合合并,如果两个数已经在同一个集合中,则忽略这个操作;

Q a b,询问编号为 a 和 b 的两个数是否在同一个集合中;

输入格式


第一行输入整数 n 和 m。


接下来 m 行,每行包含一个操作指令,指令为 M a b 或 Q a b 中的一种。


输出格式


对于每个询问指令 Q a b,都要输出一个结果,如果 a 和 b 在同一集合内,则输出 Yes,否则输出 No。


每个结果占一行。


数据范围


1≤n,m≤105


输入样例:


4 5
M 1 2
M 3 4
Q 1 2
Q 1 3
Q 3 4


输出样例:

Yes
No
Yes
1


9.2思路分析

套用模板即可,注意细节。


9.3代码实现

#include <iostream>
using namespace std;
const int N=100010;
int n,m;
int p[N];      //存储每个结点的父结点 
//返回祖宗结点+路径压缩
int find(int x){
  //如果x的父结点不是根结点,就向上走一层,看x的父结点的父结点是否是根结点,直至查找到祖宗结点(根结点) ,递归返回过程中将该路径上的结点的父结点都更新成了根结点,起到了路径压缩的作用
  if(p[x]!=x) p[x]=find(p[x]);     
  return p[x];          //返回x的父结点 
}
int main(){
  cin>>n>>m;
  //并查集初始化 ,每个结点的父结点为自己,
  for(int i=1;i<=n;i++){
  p[i]=i;
  }
  char op;
  int a,b;
  while(m--){
  cin>>op>>a>>b;
  if(op=='M') p[find(a)]=find(b);  //将a的根结点的父结点设为b的根结点 
  else{
    if(find(a)==find(b))  cout<<"Yes"<<endl;
    else cout<<"No"<<endl; 
  }
  } 
    return 0;
}


题目二

题目链接:837. 连通块中点的数量


9.1题目描述

给定一个包含 n 个点(编号为 1∼n)的无向图,初始时图中没有边。


现在要进行 m 个操作,操作共有三种:


C a b,在点 a 和点 b 之间连一条边,a 和 b 可能相等;

Q1 a b,询问点 a 和点 b 是否在同一个连通块中,a 和 b 可能相等;

Q2 a,询问点 a 所在连通块中点的数量;

输入格式


第一行输入整数 n 和 m。


接下来 m 行,每行包含一个操作指令,指令为 C a b,Q1 a b 或 Q2 a 中的一种。


输出格式


对于每个询问指令 Q1 a b,如果 a 和 b 在同一个连通块中,则输出 Yes,否则输出 No。


对于每个询问指令 Q2 a,输出一个整数表示点 a 所在连通块中点的数量


每个结果占一行。


数据范围


1≤n,m≤105


输入样例:


5 5
C 1 2
Q1 1 2
Q2 1
C 2 5
Q2 5


输出样例:


Yes
2
3


9.2思路分析

套用模板即可,注意细节。


9.3代码实现

#include <iostream>
using namespace std;
const int N=100010;
int n,m;
int p[N],num[N];      //p[]存储每个结点的父结点,num[]存储祖宗结点所在集合中结点个数(只有祖宗结点的num有意义) 
int find(int x){
  //如果x的父结点不是根结点,就向上走一层,看x的父结点的父结点是否是根结点,直至查找到祖宗结点(根结点) ,递归返回过程中将该路径上的结点的父结点都更新成了根结点,起到了路径压缩的作用
  if(p[x]!=x) p[x]=find(p[x]);     
  return p[x];          //返回x的父结点 
}
int main(){
  cin>>n>>m;
  //并查集初始化,每个结点的父结点为自己,每个根结点所在集合中结点数量为1 
  for(int i=1;i<=n;i++){
  p[i]=i;
  num[i]=1;
  }
  char op[5];
  int a,b;
  while(m--){
  cin>>op;
  if(op[0]=='C') {
    cin>>a>>b;
    if(find(a)!=find(b)){    //如果a、b不在一个集合中,将a所在集合中的元素数量累加进b所在集合中的元素数量中去 
    num[find(b)]+=num[find(a)];
    }
    p[find(a)]=find(b);  //将a的根结点的父结点设为b的根结点 
  }
  else if(op[1]=='1'){
    cin>>a>>b;
    if(find(a)==find(b)) cout<<"Yes"<<endl;
    else cout<<"No"<<endl;
  }
  else{
    cin>>a;
    cout<<num[find(a)]<<endl;
  }
  } 
    return 0;
}


十、堆

堆(STL中优先队列)是一棵 完全二叉树

c7206c45f55bf1cceb9ab1269a1f012f_dff23178703b4a75bba4f05476735344.png


小根堆:每个结点的值都小于其左右儿子的值。

a82030416c534023ef49e7d3756a43e2_3346fc79d8704c2ebe256601c3b4867d.png


下标从1开始,若从0开始会造成冲突。

1d5bd257c1580124f59db0a5bdb4b929_362b8fa96aa34126bec57001dbfdba3c.png


down和up操作时间复杂度 O(logn)。down和up操作均是对对应下标的值进行操作,并不是对下标进行down、up操作


down操作:每次与其左右儿子比较,如果比左或右儿子大,则将其中较小的交换,使其满足堆定义,否则,不交换,完成down操作。


up操作:每次与其父结点比较,若比父结点小,则交换,使其满足堆定义,否则不交换,意味着完成up操作。


建堆操作:从n/2开始,依次从下层往上层每个结点进行down操作,先完成子结点的建堆,再完成父结点的建堆,直到完成所有元素的down操作,建堆完成。


核心模板

263c8d1ed9bb998ab5537b24e05da1d1_2cc5305a15444162913092d252357a72.png


dde4b3f3b392af4055ba13d149e32841_cd81306538bd4b25991768180b9e81e5.png

e2c282a77d59ac4596f44400220e1287_c80d26e34335470cb6ba198c4ccf015b.png


//h[N]存储堆中的值,h[1]是堆顶,x的左儿子是2x,右儿子是2x+1
//ph[k]存储第k个插入的点在堆中的位置
//hp[k]存储堆中下标是k的点是第几个插入的
//num代表堆中点的数量
int h[N],ph[N],hp[k],num;
//交换两个点及其映射关系
void heap_swap(int a,int b){
    swap(ph[hp[a]],ph[hp[b]]);
    swap(hp[a],hp[b]);
    swap(h[a],h[b]);
}
void down(int u){
    int t=u;
    if(u*2<=num&&h[u*2]<h[t]) t=u*2;
    if(u*2+1<=num&&h[u*2+1]<h[t]) t=u*2+1;
    if(u!=t){
        heap_swap(u,t);
        down(t);
    }
}
void up(int u){
    while(u/2&&h[u]<h[u/2]){
        heap_swap(u,u/2);
        u>>=1;
    }
}
//O(n)建堆
for(int i=n/2;i;i--) down(i);


题目一

题目链接:838. 堆排序


10.1题目描述

输入一个长度为 n 的整数数列,从小到大输出前 m 小的数。


输入格式


第一行包含整数 n 和 m。


第二行包含 n 个整数,表示整数数列。


输出格式


共一行,包含 m 个整数,表示整数数列中前 m 小的数。


数据范围


1≤m≤n≤105 ,1≤数列中元素≤109


输入样例:


5 3
4 5 1 3 2


输出样例:


1 2 3


10.2思路分析

套用模板即可,注意细节。


10.3代码实现

#include <iostream>
using namespace std;
const int N=100010;
int n,m;
int h[N],num; //h[]存储结点的值,num存储结点个数 
void down(int u){
  int t=u;     //t存储该结点及其左右儿子中的最小值的结点编号 
  if(u*2<=num&&h[u*2]<h[t]) t=u*2;  //如果左儿子存在,且比该结点值小,更新t为左儿子
  if(u*2+1<=num&&h[u*2+1]<h[t]) t=u*2+1;  //如果右儿子也存在,且比该结点和其左儿子值小,更新t为右儿子
  //如果该结点不是最小值 
  if(u!=t){
    swap(h[u],h[t]);   //将该结点换成其左右儿子中最小值
  down(t);     //将该结点(编号已经是t,h[t]存储该结点的值)继续与下面的点比较进行down操作 
  }  
}
int main(){
  cin>>n>>m;
  for(int i=1;i<=n;i++){
  cin>>h[i];
  }
  num=n;
  //建堆 
  for(int i=n/2;i;i--) down(i);
  while(m--){
  cout<<h[1]<<' ';
  //删除堆顶元素 
  h[1]=h[num];     //用最后一个元素将堆顶元素覆盖 
  num--;           //堆中元素个数-1 
  down(1);         //对堆顶元素使用down操作 
  }
    return 0;
}


题目二

题目链接:839. 模拟堆


10.1题目描述

维护一个集合,初始时集合为空,支持如下几种操作:


I x,插入一个数 x;

PM,输出当前集合中的最小值;

DM,删除当前集合中的最小值(数据保证此时的最小值唯一);

D k,删除第 k 个插入的数;

C k x,修改第 k 个插入的数,将其变为 x;

现在要进行 N 次操作,对于所有第 2 个操作,输出当前集合的最小值。

输入格式


第一行包含整数 N。


接下来 N 行,每行包含一个操作指令,操作指令为 I x,PM,DM,D k 或 C k x 中的一种。


输出格式


对于每个输出指令 PM,输出一个结果,表示当前集合中的最小值。


每个结果占一行。


数据范围

1≤N≤105


−109≤x≤109


数据保证合法。


输入样例:


8
I -10
PM
I -10
D 1
C 2 8
I 6
PM
DM


输出样例:


-10
6


10.2思路分析

套用模板即可,注意细节。


10.3代码实现

#include <iostream>
using namespace std;
const int N=100010;
int n,m;
int h[N],num;  //h[]存储结点的值,num存储结点个数 
int ph[N],hp[N];  //ph[k]存储第k个插入的点的下标,hp[k]存储下标为k的点是第几个插入的 
void heap_swap(int a,int b){
  swap(ph[hp[a]],ph[hp[b]]);   //交换a、b 
  swap(hp[a],hp[b]);  //交换a、b的插入数(第几个插入) 
  swap(h[a],h[b]);  //交换两点的值
}
void down(int u){
  int t=u;     //t存储该结点及其左右儿子中的最小值的结点编号 
  if(u*2<=num&&h[u*2]<h[t]) t=u*2;  //如果左儿子存在,且比该结点值小,更新t为左儿子
  if(u*2+1<=num&&h[u*2+1]<h[t]) t=u*2+1;  //如果右儿子也存在,且比该结点和其左儿子值小,更新t为右儿子
  //如果该结点不是最小值 
  if(u!=t){
    heap_swap(u,t);   //将该结点换成其左右儿子中最小值的结点交换 
  down(t);     //将该结点(编号已经是t)down操作 
  }  
}
void up(int u){
  //如果该结点存在父结点,且父结点比该结点值大 
  while(u/2&&h[u/2]>h[u]){
  heap_swap(u/2,u);  //将该结点和其父结点交换 
  u/=2;        //该结点更新为其父结点,继续向上比较 
  }
}
int main(){
  cin>>n;
  while(n--){
  char op[2];
  int k,x;
  cin>>op;
  if(op[0]=='I'){
    cin>>x;
    num++;  //堆中元素数量+1 
    m++;   //m代表第几个插入的数 
    ph[m]=num;   //第m个插入的点的下标是num 
    hp[num]=m;   //下标是num的元素是第m个插入的 
    h[num]=x;    //下标是num的点的值是x 
    up(num);     //对新插入的元素进行up操作 
  }
  else if(op[0]=='P'){
    cout<<h[1]<<endl;   //输出堆中最小值 
  } 
  else if(op[0]=='D'&&op[1]=='M'){
    heap_swap(1,num);    //用最后一个元素与堆顶元素交换 
    num--;               //堆中元素个数-1
    down(1);             //对堆顶元素使用down操作 
  }
  else if(op[0]=='D'){
    cin>>k;
    k=ph[k];   //k记录需要down或up操作的点的下标
    heap_swap(k,num);   //用最后一个元素与下标为k的元素交换  
    num--;        //堆中元素个数-1,即第k个插入的点从堆中删除
    down(k),up(k);   //down和up只会走一个:如果此时k比其左右儿子值大,执行down操作,否则,执行up操作 
  }
  else{
    cin>>k>>x;
    k=ph[k];   //让k等于第k个插入点的下标
    h[k]=x;    //修改下标为k的结点的值为x 
    down(k),up(k);  //down和up只会走一个:如果此时k比其左右儿子值大,执行down操作,否则,执行up操作
  }
  } 
    return 0;
}


注意:删除操作不能这样写。

else if(op[0]=='D'){
    cin>>k;
    heap_swap(ph[k],num);    //这行可以这样写
    num--; 
    down(ph[k]),up(ph[k]);  //这行不可以,因为ph[k]的值已经在heap_swap()时改成了num,即指向的点的下标是num,而heap_swap()同时也将下标为num的点的值改成了第k个插入的点的值,而这个值已经被删除了,我们应该要down或up的是最后一个点(即与第k个点交换的那个点)。
}

原因:首先要弄清楚,我们的目的是让第k个插入的点与最后一个点交换(包括值交换,ph[]、hp[]数组的交换),然后把交换后的最后一个点删除(这时最后一个点的值已经是第k个插入的点的值了),所以只需要把堆大小-1就将“原插入的第k个点”删掉了,然后我们就需要来处理交换后的第k个插入的点,此时ph[k]存储的是最后一个插入的点的下标,而这个点的值我们由于也进行了值交换操作,这个点的值其实是存储的是第k个插入的点的值,而我们已经通过num–,来把这个点给删掉了,所以直接进行down()、up()操作就会导致错误。我们需要down()或up()的点是原来最后一个点所代表的值,也就是现在第k个插入的点的下标元素中存储的值。所以我们需要提前将这个下标来存储下来(如果不记录,要通过num来进行down()和up()操作,也会导致错误,因为num的值-1了,堆中元素已经把这个点删了,最大堆元素下标也只是num-1,所以在down()或up()操作中,也会造成错误),以便后续down()或up()操作。



帮助理解:


下图为算法基础课微信群中好兄弟分享的,帮助理解,如有侵权,立即删除。

2bc30df28ec367b9fdf327a9c32eafa0_2900b65c398a4cc195c0e87b6079bbf9.png


d2ecca28ead739c238a173b1a9c54f3e_a3781e7a552d4580a6cf034481f4b9ff.png

a6ac1ad9f79c11a3695e275ccfd101b5_3ac80702fffd41af9844046b17792aaa.png

e5b1d40323827886f39ab5d1aecce1be_8d90be40150c471eb1f6e0912c23cc14.png

十一、一般哈希

哈希:将较大范围的数映射到小范围区间内。

取模的数一般取成质数,且离2的次幂尽可能远。


拉链法:哈希值相同的,找对应位置,在对应位置的链表中插入该元素。


6d0f059e6394f75e933126aabc6b1e94_1850f78741d34df3a588a7ad795f6fca.png

开放寻址法:哈希值相同的,找对应位置,如果该位置不空,依次向后寻找,直到找到空位即可。


取模的数开到题目数据范围的2~3倍。

e55b06b9823fad119ce419f82a854ae3_fc38695e66ae471a9a902c445aa19285.png


memset按字节赋值,最常使用0和-1来memset初始化


下图作者如图,来自AcWing官网,如有侵权,立即删除。

265075a7c141a0afee68597c8f17aaac_6129eea13b1c44f9883da8735cdd7603.png


核心模板

拉链法

int h[N],e[N],ne[N],idx;
//向哈希表中插入一个数
void insert(int x){
    int k=(x%N+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;
}


开放寻址法

int h[N];
//如果x在哈希表中,返回x的下标;如果x不在哈希表中,返回x应该插入的位置
int find(int x){
    int t=(x%N+N)%N;
    while(h[t]!=null&&h[t]!=x){
        t++;
        if(t==N) t=0;
    }
    return t;
}


题目链接:840. 模拟散列表


11.1题目描述

维护一个集合,支持如下几种操作:


I x,插入一个数 x;

Q x,询问数 x 是否在集合中出现过;

现在要进行 N 次操作,对于每个询问操作输出对应的结果。

输入格式


第一行包含整数 N,表示操作数量。


接下来 N 行,每行包含一个操作指令,操作指令为 I x,Q x 中的一种。


输出格式

对于每个询问指令 Q x,输出一个询问结果,如果 x 在集合中出现过,则输出 Yes,否则输出 No。


每个结果占一行。


数据范围


1≤N≤105


−109≤x≤109


输入样例:


5
I 1
I 2
I 3
Q 2
Q 5


输出样例:


Yes


11.2思路分析

套用模板即可,注意细节。


11.3代码实现

拉链法:


#include <iostream>
#include <cstring>
using namespace std;
const int N=100003;
int h[N],e[N],ne[N],idx;  //与单链表定义类似,h[]存储每个链的头结点,e[]存储每个结点的值,ne[]存储每个结点的next指针,idx代表结点编号 
void insert(int x){
  int k=(x%N+N)%N;  //k为哈希值,先%再多+N再%N的目的:将x%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;
}
int main(){
  int n;
  cin>>n;
  memset(h,-1,sizeof h);
  char op;
  int x;
  while(n--){
     cin>>op>>x;
  if(op=='I'){
    insert(x);
  }
  else{
    if(find(x)) cout<<"Yes"<<endl;
    else cout<<"No"<<endl; 
  }
  }
  ret


开放寻址法:


#include <iostream>
#include <cstring>
using namespace std;
const int N=200003,null=0x3f3f3f3f;  //null赋值为不在题目区间内的数 
int h[N];   //h[]数组为哈希表 
int find(int x){
  int k=(x%N+N)%N;  //与上种方法同 
    //如果x应该存储的位置不空 
  while(h[k]!=null&&h[k]!=x){
      k++;    //向后寻找位置
      //如果已经找到表尾,再从表头开始找 
      if(k==N) k=0;
  }
  return k;   //如果x在表中返回其位置,如果x不在表中返回其应该存储的位置 
}
int main(){
  int n;
  cin>>n;
  memset(h,0x3f,sizeof h);   //memset按字节赋值,h为整型,4字节,则将每个字节赋值成了0x3f,即0x3f3f3f3f
  char op;
  int x;
  while(n--){
     cin>>op>>x;
     int k=find(x);    //找到x应该存储的位置
  if(op=='I'){
    h[k]=x;           //插入x 
  }
  else{
    //如果x应该存储的位置不空,输出Yes,否则输出No 
    if(h[k]!=null) cout<<"Yes"<<endl;
    else cout<<"No"<<endl; 
  }
  }
  return 0;
}


十二、字符串哈希

53c05af7f63a39746dc05daade24383e_5da7355b323c4179803bd0f22ddfd157.png

8ae1e6c787abe05b658e5aa3e301ebeb_dcb4072aa01c49258167699e9ad16246.png

88a3689e37ad7ff62879dadba7679fcd_5e866d74a3c34976b2743c5249fa226b.png



h[l-1]存储1到l-1位置子串的哈希值(子串下标从1开始),h[r]存储1到r位置子串的哈希值,要求l到r位置的哈希值:因1到l-1位置在h[l-1]和h[r]中计算哈希值时所乘权重不同(前者计算时l-1位置的权重是P0,而后者计算时r位置的权重是P0),所以要在h[r]中减去1到l-1位置的哈希值时,需要先将前l-1位置转换成在前r位置中计算时应该得到的数值(让h[l-1]乘上两者相差权重),然后再用h[r]去减去该数值。

核心思想:将字符串看成P进制数,P的经验值是131或13331,取这两个值的冲突概率低。

小技巧:取模的数用264,这样直接用unsigned long long存储,溢出的结果就是取模的结果。


核心模板

typedef unsigned long long ULL;
ULL h[N],p[N];//h[k]存储字符串前k个字母的哈希值,p[k]存储P^kmod2^64
//初始化
p[0]=1;
for(int i=1;i<=n;i++){
    h[i]=h[i-1]*P+str[i];
    p[i]=p[i-1]*P;
}
//计算子串str[l~r]的哈希值
ULL get(int l,int r){
    return h[r]-h[l-1]*p[r-l+1];
}



题目链接:841. 字符串哈希


12.1题目描述

给定一个长度为 n 的字符串,再给定 m 个询问,每个询问包含四个整数 l1,r1,l2,r2,请你判断 [l1,r1] 和 [l2,r2] 这两个区间所包含的字符串子串是否完全相同。


字符串中只包含大小写英文字母和数字。


输入格式


第一行包含整数 n 和 m,表示字符串长度和询问次数。


第二行包含一个长度为 n 的字符串,字符串中只包含大小写英文字母和数字。


接下来 m 行,每行包含四个整数 l1,r1,l2,r2 ,表示一次询问所涉及的两个区间。


注意,字符串的位置从 1 开始编号。


输出格式


对于每个询问输出一个结果,如果两个字符串子串完全相同则输出 Yes,否则输出 No。


每个结果占一行。


数据范围


1≤n,m≤105


输入样例:


8 3
aabbaabb
1 3 5 7
1 3 6 8
1 2 1 2


输出样例:


Yes
No
Yes


12.2思路分析

套用模板即可,注意细节。


12.3代码实现

#include <iostream>
using namespace std;
typedef unsigned long long ULL;
const int N=100010,P=131;
int n,m;
char str[N];
ULL h[N],p[N];  //h[i]存储前i个字母的哈希值,p[i]存储P^i%2^64的值 
//计算从l到r位置子串的哈希值 
ULL get(int l,int r){
  return h[r]-h[l-1]*p[r-l+1];
}
int main(){
    cin>>n>>m>>str+1;
    p[0]=1;
    //初始化h[]和p[] 
    for(int i=1;i<=n;i++){
      p[i]=p[i-1]*P;
      h[i]=h[i-1]*P+str[i];   //P进制数,从前往后是从高位到低位,所以每一位的权重都比其后面一位的权重多1 
  }
  while(m--){
  int l1,r1,l2,r2;
  cin>>l1>>r1>>l2>>r2;
  if(get(l1,r1)==get(l2,r2)) cout<<"Yes"<<endl;
  else cout<<"No"<<endl;
  }
  return 0;
}


十三、STL简介

ac01ff87d676ea6a61ae56ad70d6c795_fd62fa0fbde74580889376cfccab8759.png

211cfd68cc5ac62b5757c5bee49dd7e7_a0323a2b3f4e4514805a9bac588f4ed0.png

vector,变长数组,倍增思想
size()  返回元素个数
empty() 返回是否为空
clear() 清空
front()/back()
push_back()/pop_back()
begin()/end()
[]
支持比较运算,按字典序





pair<int,int>(头文件#include <utility>)
first  第一个元素
second 第二个元素
支持比较运算,以first为第一关键字,以second为第二关键字(字典序)
1

476c2f4a6016d7a647c404077b414025_38c4c7cb597b4caaace1d56ed30e43f7.png



3. string(头文件#include <string>)


size()/length()  返回字符串长度
empty()
clear()
substr(起始下标,(子串长度))  返回子串(若第二个参数省略或大于当前字符串长度,则返回整个字符串)
c_str()  返回字符串所在字符数组的起始地址
find() 查找字符第一次出现的位置,如果没有出现过返回string::npos,注意加string作用域,否则编译器不认识npos


头文件#include <string>


stoi():将字符串转化为int类型,传入string类型字符串。
atoi():将字符串转化为int类型,传入char类型字符串。
to_string():将数字常量量转化为string类型字符串。
//注意字符利用ASCII码进行过转换时(加减数字),需要强制类型转换。

4acc06090eca2e062b2c95fb759ba1c9_f29b4f09d67a484d9dc88e17655142ee.png

9347728f96a93bec813dc2b7c1e4dcb2_cdf8c26577bd4096885769fbaf912c49.png

fc02177e7157750f5699393d655b3c95_00063e2ce06545598465e3ea3b4ddca1.png




queue,队列(头文件#include <queue>)
size()
empty()
push()  向队尾插入一个元素
front() 返回队头元素
back()  返回队尾元素
pop()   弹出队头元素


新建空队列(清空队列q)


1ba37ca442c05b1d944f52b60548ceaa_e9c9ffe902194c33ae723bb5d77ebfd6.png

priority_queue,优先队列,默认是大根堆(头文件#include <queue>)
size()
empty()
push()  插入一个元素
top()   返回堆顶元素
pop()   弹出堆顶元素
定义成小根堆的方式:priority_queue<int,vector<int>,greater<int>>q;
(也可以将正数变成相反数插入堆,得到的便是每个元素是负数的小根堆,需要操作时,取出元素加负号就是原序列的元素)

1573f76b2b998f02c549aa0d11ba8196_6542e6754e2543d29accb1c549483c55.png



6. stack,栈(头文件#include <stack>)


size()
empty()
push()  向栈顶插入一个元素
top()   返回栈顶元素
pop()   弹出栈顶元素


deque,双端队列(头文件#include <deque>)
size()
empty()
clear()
front()/back()
push_back()/pop_back()
push_front()/pop_front()
begin()/end()


set,map,multiset,multimap,基于平衡二叉树(红黑树),动态维护有序序列,set、map去重,multise、multimap不去重(头文件#include <set>、#include <map>)
size()
empty()
clear()
begin()/end()
++,--返回前驱和后继,时间复杂度O(logn)
有重复元素,map不会插入新元素,所以旧元素不会被覆盖更新


set/multiset(头文件#include <set>)
insert()  插入一个数
find()    查找一个数,如果找到返回该数第一次出现的位置的迭代器,否则返回容器尾迭代
count()   返回某一个数的个数
erase()
  (1)输入是一个数x,删除所有x   O(k+logn)(k为x的个数)
  (2)输入一个迭代器,删除这个迭代器
lower_bound()/upper_bound()
   lower_bound(x)  返回大于等于x的最小的数的迭代器
   upper_bound(x)  返回大于x的最小的数的迭代器

d264006a278baede5fd7c9f61a5b309b_4cda09cb8a0a4f7f9e30f4a6f6082e35.png

10. map/multimap(头文件#include <map>)


insert()  插入的数是一个pair
erase()   输入的参数是pair或者迭代器
find()    同上
[]  注意multimap不支持此操作。时间复杂度O(logn)
lower_bound()/upper_bound()

b2fc43f06320fe69c584b086d8c9d74b_83c4fe50068b47f1801bce39af0e69bf.png

00561dc55d7bb01a9829e13c7dff6a37_154a9c610ccb44e6a2ee684752a556fe.png

78454277116e0064e50543778133a090_97847282e1b74d11952e35f41f6a4979.png



unordered_set,unordered_map,unordered_multiset,unordered_multimap,哈希表(头文件#include <unordered_set>、#include <unordered_map>)
和上面类似,增删改查的时间复杂度为O(1)
不支持 lower_bound()/upper_bound(),迭代器的++,--


bitset,压位(头文件#include <bitset>)
bitset<10000> s;    
~,&,|,^
>>,<<
==,!=
[]
count()  返回有多少个1
any()    判断是否至少有一个1
none()   判断是否全为0
set()    把所有位置成1
set(k,v) 把第k位变成v
reset()  把所有位变成0
flip()   等价于~
flip(k)  把第k位取反

082f64f71a5edfe23360253793767338_35119a8a50da4e5b92d63ce015529809.png

1bd7799f439fd16b080dbb20b459dab8_575ec4235d974bdd8722eca321738988.png

目录
相关文章
|
4天前
|
算法
数据结构中的KMP算法及其改进算法
KMP算法通过引入部分匹配表,有效避免了重复计算,从而将字符串匹配的时间复杂度降低到O(m+n)。通过进一步优化next数组,KMP算法的效率得到了进一步提升。对于大规模字符串匹配问题,KMP算法及其改进算法提供了高效的解决方案,是计算机科学领域的经典算法之一。
11 3
|
9天前
|
存储 算法
数据结构和算法——散列表的性能分析(开放地址法的查找性能、期望探测次数与装填因子的关系、分离链接法的查找性能)
数据结构和算法——散列表的性能分析(开放地址法的查找性能、期望探测次数与装填因子的关系、分离链接法的查找性能)
11 0
|
9天前
|
存储 算法 NoSQL
数据结构和算法——哈希查找冲突处理方法(开放地址法-线性探测、平方探测、双散列探测、再散列,分离链接法)
数据结构和算法——哈希查找冲突处理方法(开放地址法-线性探测、平方探测、双散列探测、再散列,分离链接法)
11 1
|
9天前
|
算法
数据结构和算法——散列函数的构造方法(直接定址法、除留余数法、数字分析法、折叠法、平方取中法、ASCII码加和法、前三字符移位法)
数据结构和算法——散列函数的构造方法(直接定址法、除留余数法、数字分析法、折叠法、平方取中法、ASCII码加和法、前三字符移位法)
7 0
|
9天前
|
存储 算法
数据结构和算法——了解哈希表(哈希查找、散列的基本思想)
数据结构和算法——了解哈希表(哈希查找、散列的基本思想)
12 0
|
9天前
|
搜索推荐 算法 Shell
数据结构和算法——排序算法的比较和排序综测测验
数据结构和算法——排序算法的比较和排序综测测验
6 0
|
9天前
|
算法 C语言
数据结构和算法——桶排序和基数排序(图示、伪代码、多关键字排序,基数排序代码)
数据结构和算法——桶排序和基数排序(图示、伪代码、多关键字排序,基数排序代码)
9 0
|
4天前
|
存储 算法 调度
数据结构与算法-栈篇
数据结构与算法-栈篇
12 3
|
1天前
数据结构 栈 / 队列(第9天)
数据结构 栈 / 队列(第9天)
|
1天前
|
存储 算法 程序员
【C++进阶】深入STL之 栈与队列:数据结构探索之旅
【C++进阶】深入STL之 栈与队列:数据结构探索之旅