int son[N][26], cnt[N], idx;// 0号点既是根节点,又是空节点// son[][]存储树中每个节点的子节点// cnt[]存储以每个节点结尾的单词数量// 插入一个字符串voidinsert(char*str){int p =0;for(int i =0; str[i]; i ++){int u = str[i]-'a';if(!son[p][u]) son[p][u]=++ idx;
p = son[p][u];}
cnt[p]++;}// 查询字符串出现的次数intquery(char*str){int p =0;for(int i =0; str[i]; i ++){int u = str[i]-'a';if(!son[p][u])return0;
p = son[p][u];}return cnt[p];}
使用数组模拟指针,下标是x的点,这个节点所有的儿子,son[x][0]表示第1个儿子。
#include<iostream>usingnamespace std;constint N =100010;int son[N][26];// 每个节点的子节点个数最多是26,每个节点最多向外连26条边int cnt[N];// 以当前点结尾的单词有多少个,存储以每个节点结尾的单词数量int idx =0;// 表示当前用到哪个下标(节点),下标为0的点,既是根节点,又是空节点// 存储插入voidinsert(char str[]){int p =0;// 从根节点开始,p的范围是0~9999,因为字符串总长度不超过100000for(int i =0; str[i]; i++)// C++字符串结尾是\0, str[]判断是否走到结尾{int u = str[i]-'a';// 把字母a~z映射成数字0~25if(!son[p][u]) son[p][u]=++idx;// p这个节点不存在u这个儿子的话,就创建出来
p = son[p][u];// 走到下一个点,因为son[p][u]的值会一直+1}
cnt[p]++;// 以这点为结尾的单词数量多了一个}// 查询字符串出现多少次intquery(char str[]){int p =0;for(int i =0; str[i]; i++){int u = str[i]-'a';// 得到当前查询字母子节点的编号if(!son[p][u])return0;// 如果不存在当前字母,直接return 0
p = son[p][u];//存在当前字母就走下去}return cnt[p];//返回以p结尾的单词数量}
int p[N];//存储每个点的祖宗节点// 返回x的祖宗节点intfind(int x){if(p[x]!= x) p[x]=find(p[x]);return p[x];}// 初始化,假定节点编号是1~nfor(int i =1; i <= n; i ++) p[i]= i;// 合并a和b所在的两个集合:
p[find(a)]=find(b);
2.3.2 维护size的并查集
int p[N], size[N];//p[]存储每个点的祖宗节点, size[]只有祖宗节点的有意义,表示祖宗节点所在集合中的点的数量// 返回x的祖宗节点intfind(int x){if(p[x]!= x) p[x]=find(p[x]);return p[x];}// 初始化,假定节点编号是1~nfor(int i =1; i <= n; i ++){
p[i]= i;
size[i]=1;}// 合并a和b所在的两个集合:
size[find(b)]+= size[find(a)];
p[find(a)]=find(b);
2.3.3 维护到祖宗节点距离的并查集
int p[N], d[N];//p[]存储每个点的祖宗节点, d[x]存储x到p[x]的距离// 返回x的祖宗节点intfind(int x){if(p[x]!= x){int u =find(p[x]);
d[x]+= d[p[x]];
p[x]= u;}return p[x];}// 初始化,假定节点编号是1~nfor(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)的偏移量
// 老师讲解中所有的size都不能用,用其他代替#include<iostream>#include<algorithm>// swap函数#include<cstring>#include<stdio.h>// C的头文件usingnamespace std;constint N =100010;int h[N], asize =0;// heap[]数组int ph[N], hp[N];// ph[k]表示第k个插入的数在堆里面的下标是什么,hp[k]表示堆里面第k个点是第几个插入的点// 交换,进行存储映射(ph[i]和ph[j]所指的数)voidheap_swap(int a,int b)// a和b均表示堆里面元素的下标{// 如上图存在 ph[j] = k, hp[k] = j的映射关系// 这里是hp[a] = i, ph[i] = a,hp[b] = j, ph[j] = b,所以交换ph[]所指的方向swap(ph[hp[a]], ph[hp[b]]);swap(hp[a],hp[b]);swap(h[a],h[b]);}// down函数voiddown(int u){int t = u;// 先假设父节点为最小值,u表示下标标号if(u *2<= asize && h[u *2]< h[t]) t = u *2;// 左儿子小于父节点,则最小值t为左儿子if(u *2+1<= asize && h[u *2+1]< h[t]) t = u *2+1;if(u != t){heap_swap(u, t);// 这里u是父节点,t是子节点,不等于的话说明子节点比父节点值小,所以要交换down(t);}}voidup(int u){while(u /2&& h[u /2]> h[u])// 父节点大于子节点,就交换{heap_swap(u /2, u);
u = u /2;}}intmain(){int n, m =0;// m表示当前第几个插入的数scanf("%d",&n);while(n --){char op[10];int x, k;scanf("%s", op);// 操作1: 将输入的数插入到堆的末尾,再进行up操作if(!strcmp(op,"I"))// 字符串比较,strcmp(a, b),比较两个字符串的大小,a < b 返回-1,a == b 返回0,a > b返回1 {scanf("%d",&x);// 把插入的数读进来
asize ++;// 表示堆里面增加一个元素
m ++;// 当前的x是第m个插入的数, size和m都是先+1,再进行下面操作,因为m和size都要从1开始
ph[m]= asize, hp[asize]= m;// 二者进行映射
h[asize]= x;up(asize);}// 操作2: 输出当前集合的最小值elseif(!strcmp(op,"PM"))printf("%d\n",h[1]);// 操作3: 删除当前集合中的最小值elseif(!strcmp(op,"DM")){heap_swap(1,asize);// 堆顶元素换成堆的末尾元素,直接将最后一个数和第1个交换
asize--;down(1);// 将堆顶元素down下来}// 操作4: 删除第 k 个插入的数elseif(!strcmp(op,"D")){scanf("%d",&k);// 第k个插入的数int a;
a = ph[k];// ph[k]表示第k个插入的数在堆的下标位置heap_swap(a, asize);// 删除就是将第k个数和末尾元素进行交换
asize--;down(a),up(a);// 将位置a的元素down下来,保证堆顶元素是最小值,down和up函数只会执行一个}// 操作5: 修改第 k 个插入的数,将其变为 xelseif(!strcmp(op,"C"))// 这边是两个字符串作比较{scanf("%d%d",&k,&x);int b;
b = ph[k];
h[b]= x;down(b),up(b);}}return0;}
4 一般哈希
将一个-10e9到10e9之间的数映射到0到10e5
离散化是特殊的哈希方式(需要排序单调递增),这里讲的是一般哈希
哈希表有两个操作:插入数字,查找数字
4.1 模板
4.1.1 拉链法
mod后面的数尽量取质数,这样发生冲突的概率会小
// x % N可能是正数也可能是负数,取决于x正负,再加上N就一定是正数。N表示质数int k =(x % N + N)% N;// 为了让哈希值k变成正数,k也是头结点下标
e[idx]= x;// 将k赋值给链表
ne[idx]= h[k];// k指向下一个点变成新插入的点指向下一个点
h[k]= idx;
int h[N], e[N], ne[N], idx;// 向哈希表中插入一个数voidinsert(int x){int k =(x % N + N)% N;
e[idx]= x;
ne[idx]= h[k];
h[k]= idx ++;}// 在哈希表中查询某个数是否存在boolfind(int x){int k =(x % N + N)% N;for(int i = h[k]; i !=-1; i = ne[i])if(e[i]== x)returntrue;returnfalse;}
类似单链表插入
// 将x值插到头结点(头插法)voidadd_to_head(int x)// idx表示插入点的下标值,插入的第1个点idx = 0{// head 表示头结点的下标,存储链表头,head的存储值时0,head——>0// ne[ ]存储节点的next指针,就是ne[k]里面存的是k+1,就是ne[k] = k + 1
e[idx]= x;// 将x的值存下来
ne[idx]= head;// 红色指针1指向之前head存的,之前是head指向节点0,现在换成ne[idx]指向节点0, 这里的ne[idx]存储的就是idx+1
head = idx;// 将head指向红色指针2,相当于 head ——> idx,idx = 0
idx ++;}
4.1.2 开放寻址法
int h[N];// 如果x在哈希表中,返回x的下标;如果x不在哈希表中,返回x应该插入的位置intfind(int x){int t =(x % N + N)% N;while(h[t]!= null && h[t]!= x){
t ++;if(t == N) t =0;}return t;}
#include<cstdio>#include<algorithm>#include<cstring>#include<iostream>#include<vector>using namepspace std;intmain(){
vector<int>a;for(int i =0; i <10; i++) a.push_back(i);// 方式1for(int i =0; i < a.size(); i++) cout << a[i]<< endl;// 方式2 迭代器for(vector<int>::iterator i =begin(); i !=end(); i++)cout <<*i << endl;for(auto i =begin(); i !=end(); i++)cout <<*i << endl;// auto方式// a.begin() 就是a[0],a.end()就是a[a.size()]最后一个数的后面一位// 方式3for(auto x : a) cout << x << endl;}