文章目录
- 哈希表存储结构
- 拉链法:
- 插入
- 查询
- 题目
- 注意
- 开放寻址法
- 查找质数
- 代码
- 字符串哈希方式
- STL相关知识
哈希表存储结构
整体结构
0~109->0~105
方法:
- x mod 105
- 处理冲突
- 开放寻址法
- 拉链法
拉链法:
思想:每个槽上拉一条链,用于储存该槽上所有数
插入
- 确定插槽
- 确定插槽链表
- 插入至头节点
查询
- 确定插槽
- 遍历插槽链表
题目
维护一个集合,支持如下几种操作:1. `I x`,插入一个数 xx;
2. `Q x`,询问数 xx 是否在集合中出现过;现在要进行 N 次操作,对于每个询问操作输出对应的结果。输入格式第一行包含整数 N,表示操作数量。接下来 N 行,每行包含一个操作指令,操作指令为 `I x`,`Q x` 中的一种。输出格式对于每个询问指令 `Q x`,输出一个询问结果,如果 xx 在集合中出现过,则输出 `Yes`,否则输出 `No`。每个结果占一行。数据范围1≤N≤1051≤N≤105
−109≤x≤109−109≤x≤109输入样例:
5
I 1
I 2
I 3
Q 2
Q 5
输出样例:
Yes
No
#include <iostream>
#include <cstring>
using namespace std;
const int N = 100003;//e被拆成了很多链
int h[N],e[N],ne[N],idx;
//插入到链表头部
void insert(int x)
{//将k映射至题目范围int k = (x % N + N)%N;//插入:单链表插入//h[k]代表k链的头部节点,节点值为xe[idx] = x,ne[idx] = h[k],h[k] = idx++;
}
bool find(int x)
{int k = (x%N+N)%N;/*遍历,从头指针开始,头指针存储第一个结点的下标,e[i]表示当前节点的值是多少ne[i]下一个节点的下标空指针的下标为-1*/for(int i = h[k];i!=-1;i = ne[i])if(e[i] == x)return true;return false;
}int main()
{/*寻找大于100000的最小质数//1.确定范围for(int i =100000;;i++){//2. 设置标志位bool flag = true;//3. 从2开始查找,条件为平方小于ifor(int j = 2;j*j<=i;j++){if( i % j == 0 ){//4. 设置标志flag = false;break;}}//5. 判断标志if(flag){cout<< i<<endl;//6. 结束break;}}return 0;*/int n;scanf("%d",&n);//槽清空memset(h,-1,sizeof(h));while(n--){//注意此处为数组输入%schar op[2];int x;scanf("%s%d",op,&x);if(*op == 'I') insert(x);else{if(find(x)) puts("Yes");elseputs("No");}}
}
注意
- 取模计算
负数模一个值等于该绝对值模值结果添加负号
-10 %3 = -1
需要先模再加再模,才可以使得负数取模为正数。
开放寻址法
只开了一个一维数组,没有开辟链表,形式更为简单。
一维数组的长度经验来说开到题目的两到三倍
如何处理冲突:从第k个坑位开始找,如果第k个坑位有人的话就查找下一个坑位
一般题目只会用到查找与添加
添加
查找:从前向后找
删除:找到x后打个标记,表示x删除
维护一个集合,支持如下几种操作:1. `I x`,插入一个数 xx;
2. `Q x`,询问数 xx 是否在集合中出现过;现在要进行 N 次操作,对于每个询问操作输出对应的结果。输入格式第一行包含整数 N,表示操作数量。接下来 N 行,每行包含一个操作指令,操作指令为 `I x`,`Q x` 中的一种。输出格式对于每个询问指令 `Q x`,输出一个询问结果,如果 xx 在集合中出现过,则输出 `Yes`,否则输出 `No`。每个结果占一行。数据范围1≤N≤1051≤N≤105
−109≤x≤109−109≤x≤109输入样例:
5
I 1
I 2
I 3
Q 2
Q 5
输出样例:
Yes
No
查找质数
#include <iostream>using namespace std;
//开两倍
const int N = 200003;int main()
{//首先找到大于200000的质数for(int i = 200000;;i++){bool flag = true;for(int j = 2; j*j<=i ;j++)if(i%j ==0){flag = false;break;}if(flag){cout<<i<<endl;break;}}return 0;
}
得到结果为 200003
代码
#include <iostream>
#include <cstring>
using namespace std;
//N为大于2倍的最小质数,null为大于区间的一个值,用于表示空
//一般设置最大值也设置0x3f3f3f3f
const int N = 200003,null = 0x3f3f3f3f;
int h[N];//find--寻找下一个坑位
int find(int x)
{//映射int k = (x % N + N) % N;//坑位有人并且不等于x//循环退出条件:找到值或者找到新的坑位//一定可以停止:因为数组大小较大while(h[k] != null && h[k] != x ){//向后查找下一个坑位k++;//如果已经到达结尾,循环看第一个坑位if(k == N) k = 0;}//如果x在哈希表中,k返回为下标;//如果不在哈希表中,k表示应该存储的位置return k;
}int main()
{int n;int x;char op[2];/*为什么memset 0x3f因为memset按字节进行,不是按数字初始化的,h是int型数组,一共有4个字节,每个字节都是0x3f,因此每个数是0x3f3f3f3f.一个字节是八位。memset(h,0,sizeof(h)); 每个字节是0,整体为0memset(h,-1,sizeof(h)); 每个字节是-1(1111 1111),整体为-1*/memset(h,0x3f,sizeof(h));scanf("%d",&n);while(n--){scanf("%s%d",op,&x);int k = find(x);if(op[0] == 'I') h[k] = x;else {if(h[k] != null) puts("Yes");else puts("No");}}return 0;
}
字符串哈希方式
字符串前缀哈希法
步骤:
-
预处理所有前缀的哈希
-
如何定义某个前缀的哈希值
把字符串看成P进制的数。
假定映射关系如下表
字母 值 A 1 B 2 C 3 D 4 字符串“ABCD”对应的数值如下图所示:
-
预处理方式
-
将P进制的数转化为10进制的数;数字可能很大,通过取模进行映射到
0,Q-1
-
注意:
-
不能把字母映射为0。不然不同的字符串均映射为0导致出错与冲突
-
Rp【P进制】足够好,不存在冲突。经验值:131或13331
好处:可以利用前缀哈希,求得所有子串 的哈希值
已知h[R]、h[L-1]
-
h[L-1]
左移至与R对齐 -
然后相减: h[R]-h[L-1]+PR-L+1
-
Q:264
用unsigned long long存储h,当数值溢出相当于取模,因此不需要取模
给定一个长度为 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
#include <iostream>
#include <string>typedef unsigned long long ULL;const int N = 100010,P = 131;int n,m;
char str[N];
/*
p数组用来存储p的多少次方的数值,p的0次方等于1
h数组表示某个前缀的哈希值,h[R]表示前R个字母的哈希值
*/
ULL h[N],p[N];
ULL get(int l,int r)
{return h[r] - h[l-1] * p[r-l+1]; //部分区间长度计算公式
}
int main()
{scanf("%d%d%s",&n,&m,str + 1);p[0] = 1;for(int i = 1;i <= n ;i++){p[i] = p[i-1] * P; //填充进制 P的一次方 P的二次方h[i] = h[i-1] * P+ str[i]; //填充前缀和}while(m--){int l1,r1,l2,r2;scanf("%d%d%d%d",&l1,&r1,&l2,&r2);if(get(l1,r1) == get(l2,r2)) puts("Yes");else puts("No");}return 0;
}
STL相关知识
/*
vector:变长数组,数组长度动态变化,倍增的思想
string:字符串,substr(),c_str()
queue:push(),front(),pop()
priority queue,优先队列,push(),top(),pop()
stack,栈,push(),top(),pop()
deque,双端队列
set,map, multiset,multimap,基于平衡树二叉树(红黑树)实现,动态维护有序序列
unordered_set,unordered_map,unordered_multiset,unordered_multimap,哈希表
bitset,压位
*/
一开始学习的时候,现用现查就可以了
vector的倍增思想
系统为某一个程序分配空间时,所需的时间基本上与空间大小无关,与申请次数有关
vector数组长度不够时,新建一个二倍长度的数组,然后把之前的数据copy过来
/*知道存在该操作,用的时候现用现查
vector:变长数组,数组长度动态变化,倍增的思想size(); //返回元素个数empty(); //返回a是否为空clear():清空,这个函数不是所有容器都有front()/back()push_back()/pop_back()begin()/end()[] 随机取址支持比较运算
pair<int,int> 用于存储二元组first,第一个元素second, 第二个元素支持比较运算,以first为第一个关键字,以second为第二个关键字(字典序)pair与结构体相比有什么好处pair可以看作帮我们实现了一个结构体,自带一个比较函数,比一般结构体省代码可以存储任意多个,可以随便嵌套
string:字符串,substr(),c_str()size()/length() 返回字符串长度empty()clear()
queue:队列,队尾插入,队头弹出,先入先出的关系size()empty()push() 向队尾插入一个元素front() 返回队头元素back() 返回队尾元素pop() 弹出队头元素没有clear函数,如果需要清空一个queue,直接构造一个queue就可以定义时小根堆,多加两个参数priority_queue<int,vector<int>,greater<int>> heap;
priority queue,优先队列,默认时大根堆push(),插入一个元素top(),返回堆顶元素pop(),弹出堆顶元素
stack,栈,size()empty()push(),向栈顶插入一个元素top(),返回栈顶元素pop(),弹出栈顶元素
deque,双端队列size()empty()clear()front()back()push_back() / pop_back()push_front() / push_front()begin() / end()[]deque用的比较少,以为deque的效率低的令人发指,比一般的慢好几倍
set,multiset,map, multimap,基于平衡树二叉树(红黑树)实现,动态维护有序序列size()empty()clear()begin()/end() ++,-- 返回前驱和后继 时间复杂度 O(logn)set/multisetset不支持插入重复元素multiset 支持插入重复元素insert() 插入一个数find() 查找一个数count() 返回某个数的个数erase()(1)输入是一个数,删除所有x O(k + log(n))//k为数的个数(2)输入一个迭代器,删除这个迭代器lower_bound()/upper_bound()lower_bound(x) 返回大于等于x的最小的数的迭代器upper_bound(x) 返回大于x的最小的数的迭代器map/multimapinsert() 插入的数是一个pairerase() 插入的参数是pair或者迭代器find()[] 时间复杂度是O(logn)lower_bound()/upper_bound()unordered_set,unordered_map,unordered_multiset,unordered_multimap,哈希表和上面类似,增删改查的时间复杂度是O(1)不支持lower_bound()/upper_bound(),及迭代器的++ --
bitset,压位一个整数,4个字节,32位,很多题目压位计算bitset1024 bool数组 C++中一个bool是一个字节1024B = 1KB移到位中去,只需要开128B,比正常数组省8倍内存bitset<10000> s; <>中为个数~s,&,|,^>>,<<==,!=[]count() 返回多少个1any/none()any() 判断是否至少有一个1none() 判断是否全为0set() 把所有位置置为1set(k,v) 将第k位变为vreset() 把所有位变为0flip() 等价于~flip(k) 把k位取反C++ Reference C++最权威语法
*/
#include <cstdio>
#include <string>
#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>
#include <deque>
#include <set>
#include <map>
#include <unordered_map>
using namespace std;int main()
{/*vector<int> a;vector<int> b(10);vector<int> c(10,-3);for(auto x :c )cout<<x<<endl;vector<int> a[10];//size与empty是所有元素都有的函数,所有容器都有//时间复杂度是O(1)的,不会说求size的时候遍历,而是有一个变量来存储sizea.size(); //返回元素个数a.empty(); //返回a是不是空的*//*vector遍历vector<int> a;for(int i = 0 ;i < 10;i++) a.push_back(i);for(int i = 0;i<a.size();i++) cout<<a[i]<<' ';cout<<endl;//迭代器可以看做是指针,使用*解引用//for(vector<int>::iterator i = a.begin();i != a.end();i++) cout<<*i<<' ';//auto关键字,系统自动推断类型,当类型特别长时,用auto很省事for(auto i = a.begin();i != a.end();i++) cout<<*i<<' ';cout<<endl;//a.begin==a[0] a.end()==a.[a.size()]//C++中范围遍历for(auto x : a) cout<<x<<' ';cout<<endl;return 0;//比较运算。按照字典序进行比较vector<int> a(4,3),b(3,4);if(a < b) puts("a<b");*//** pair<int,string> p;* 某个东西有两个不同的属性,就可以用pair存储,可能需要按照某一个属性排序p.firstp.second有三个属性也可以用pair存储pair<int,pair<int,int> > ppair<int,string> p;p = make_pair(10,"jgy");p = {20,"abc"};* *//** string a = "yxc";a += "def";a += 'c';// cout<< a <<endl;//含义:下标从1开始,返回n个字母;当n大于数组长度时,输出到字符串最后一个字符为止// 把第二个参数省略,返回从1开始的整个子串// cout<<a.substr(1,10)<<endl;printf("%s\n",a.c_str());* *//*queue清空* queue<int> a;a = queue<int>();* *//* heap默认大根堆* priority_queue<int> heap;* 如果要使用小根堆,* 1. 直接插入-x* 2. 定义时直接定义为小根堆,多加两个参数* //小根堆priority_queue<int,vector<int>,greater<int>> heap;* *//*集合* set<int> s;multiset<int> ms;* *//*map* map<string ,int> a;a["yxc"] = 1;cout<<a["yxc"]<<endl;* *//*unordered_map<string,int > a;unordered_multimap<string,int > b;return 0;* *//*bitset* 10000*10000 bool* 10的8次方 100MB空间 题目限制64M* bitset 12M足够了* */}
bitset
1024 bool数组 C++中一个bool是一个字节
1024B = 1KB
移到位中去,只需要开128B