哈希表
①存储结构:开放寻址法、拉链法
②字符串哈希方式
一般是通过取模,Mod 一个质数(且尽量离2的整数幂远) 可能发生冲突,所以用开放寻址法、拉链法
对于拉链法,在每个槽上拉一条链,冲突就在链上加
对于开放寻址法,只开了一个一维数组,范围在题目范围的2-3倍。开到一个位置,如果这个槽有数就下一个,直到找到第一个空的位置。
对于字符串哈希,如STR:“ABCABC../”,则h[0]=0,h[1]="A",h[2]="AB",h[3]="ABC"...
ABCD->(1,2,3,4)p=(1*p3+2*p2+3*p1+4*p0) mod Q
Tip:不能映射成0
若不存在冲突,对于P的经验值是131或13331,则Q取2 的16次方
对于区间【1~L~R】,已知1-L,1-R的哈希,求L~R的哈希:
左边是高位,右边是低位,那么1处是R-1,R处是0
公式是h[R]-h[L]*P^(R-L+1)
核心思想:将字符串看成P进制数,P的经验值是131或13331,取这两个值的冲突概率低
小技巧:取模的数用2^64,这样直接用unsigned long long存储,溢出的结果就是取模的结果typedef unsigned long long ULL;
ULL h[N], p[N]; // h[k]存储字符串前k个字母的哈希值, p[k]存储 P^k mod 2^64// 初始化
p[0] = 1;
for (int i = 1; i <= n; i ++ )
{
h[i] = h[i - 1] * P + str[i - 1];
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];
}
1.模拟散列表
①拉链法
//拉链法,其实本质上就是N个单链表,就是头结点head变成了h[i]
#include <iostream>
#include <cstring>
using namespace std;
const int N = 100003; // 取大于1e5的第一个质数,取质数冲突的概率最小
//h是一个数组,对应索引上面连着一个链表,h[i]的值是i索引上的链表中的头结点的下一个元素的指针,头结点是留空的; h[N]是槽,e[N]是链表的值,ne[N]是下一个的位置
int h[N], e[N], ne[N], idx;
//插入的方式是往头结点插入,具体可以看单链表
void insert(int x) {
int k = (x % N + N) % N;//+N,%N 是防止x是负数
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); //将h数组全部元素的值赋为-1
while (n--) {
string op;
int x;
cin >> op >> x;
if (op == "I") insert(x);
else {
if (find(x)) cout<<"Yes"<<endl;
else cout<<"No"<<endl;
}
}
return 0;
}
②开放寻址法
//开放寻址法
#include <cstring>
#include <iostream>
using namespace std;
//开放寻址法一般开 数据范围的 2~3倍, 这样大概率就没有冲突了
const int N = 200003; //大于数据范围的第一个质数
const int null = 0x3f3f3f3f; //规定空指针为 null 0x3f3f3f3f
int h[N];
//插入与查找合为一体的函数
int find(int x) {
int k = (x % N + N) % N;
//h[k] != null是插入操作的条件,h[k] != x是查找操作的条件
//跳出循环的条件h[k] == null既是插入位置的最终结果,也是未找到待查找元素的最终结果
//这里可能会考虑用for循环代码更短,但提交可能会超时,采用while循环会更快
while ( h[k] != null && h[k] != x ) {
k++;
if (k == N) k = 0;
}
return k;
}
int main() {
int n;
cin >> n;
memset(h, 0x3f, sizeof h); //规定空指针为 0x3f3f3f3f
while (n--) {
string op;
int x;
cin >> op >> x;
if (op == "I") {
h[find(x)] = x;
} else {
if (h[find(x)] == x) {
puts("Yes");
} else {
puts("No");
}
}
}
return 0;
}
2.字符串哈希
#include <iostream>
using namespace std;
const int N = 100010,P = 131;//P=131 或 P=13331
typedef unsigned long long ull;//因为Q是2的64次方
char str[N];
//p[i]是131的i次方,主要就是用到一个p[r - l + 1];h[i]是指前i个字母的哈希值,p和h下标都从1开始考虑
ull h[N],p[N];
ull get(int l,int r){
return h[r] - h[l - 1] * p[r - l + 1];
}
int main(){
int n,m;
cin>>n>>m>>str;
p[0] = 1;
//初始化
for(int i = 1; i <= n; i++){
p[i] = p[i-1] * P;
h[i] = h[i-1] * P + str[i - 1];//计算字符串哈希值的公式
}
while(m--){
int l1,r1,l2,r2;
cin>>l1>>r1>>l2>>r2;
if(get(l1,r1) == get(l2,r2)) puts("Yes");
else puts("No");
}
return 0;
}
STL
vector, 变长数组,倍增的思想,自动变长
系统为每一程序分配空间时候,所需的时间与空间大小无关。与申请次数有关,所以变长数组要减少申请次数,每次数组长度不够就将长度*2
最终申请10^6时,1+2+4+...+5*10^5=10^6
size() 返回元素个数
empty() 返回是否为空
clear() 清空
front()/back() 返回第一个数/最后一个数
push_back()/pop_back() 在最后插入一个数/把最后一个数删掉
begin()/end() 第0个数/最后一个数的后一个数
[]
在前面#include <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 end1;//数组下标遍历
for (vector<int>:: itreator i=i.begin(); i != a.end(); i++) cout << *i << ' '; cout << end1;//迭代器遍历
for (auto x :a ) cout << x << ' '; cout << end1; //范围遍历,效率快一些
支持比较运算,按字典序
pair<int, int>//定义
first, 第一个元素
second, 第二个元素
支持比较运算,以first为第一关键字,以second为第二关键字(字典序)初始化:pair<int,string>p;
p = make_pair(10,"yxc"); /p =(20,"abc");
存三个不同东西:pair<int,pair<int,int>>p;
string,字符串
size()/length() 返回字符串长度
empty()
clear()
substr(起始下标,(子串长度)) 返回子串
c_str() 返回字符串所在字符数组的起始地址string a ="sss";
a+="def";//可以在后面加
queue, 队列
size()
empty()
push() 向队尾插入一个元素
front() 返回队头元素
back() 返回队尾元素
pop() 弹出队头元素想清空直接重新创建就行,没有clear
priority_queue, 优先队列,默认是大根堆
size()
empty()
push() 插入一个元素
top() 返回堆顶元素
pop() 弹出堆顶元素
定义成小根堆的方式:priority_queue<int, vector<int>, greater<int>> q;定义:priority_queue<int> heap;
stack, 栈
size()
empty()
push() 向栈顶插入一个元素
top() 返回栈顶元素
pop() 弹出栈顶元素
deque, 双端队列
size()
empty()
clear()
front()/back()
push_back()/pop_back()
push_front()/pop_front()
begin()/end()
[]
set, map, multiset, multimap, 基于平衡二叉树(红黑树),动态维护有序序列
size()
empty()
clear()
begin()/end()
++, -- 返回前驱和后继,时间复杂度 O(logn)
set/multiset
insert() 插入一个数
find() 查找一个数
count() 返回某一个数的个数
erase()
(1) 输入是一个数x,删除所有x O(k + logn)
(2) 输入一个迭代器,删除这个迭代器
lower_bound()/upper_bound()
lower_bound(x) 返回大于等于x的最小的数的迭代器
upper_bound(x) 返回大于x的最小的数的迭代器
map/multimap
insert() 插入的数是一个pair
erase() 输入的参数是pair或者迭代器
find()
[] 注意multimap不支持此操作。 时 map/multimap间复杂度是 O(logn)
lower_bound()/upper_bound()
unordered_set, unordered_map, unordered_multiset, unordered_multimap, 哈希表
和上面类似,增删改查的时间复杂度是 O(1)
不支持 lower_bound()/upper_bound(), 迭代器的++,--
bitset, 圧位
bitset<10000> s;
~, &, |, ^
>>, <<
==, !=
[]count() 返回有多少个1
any() 判断是否至少有一个1
none() 判断是否全为0set() 把所有位置成1
set(k, v) 将第k位变成v
reset() 把所有位变成0
flip() 等价于~
flip(k) 把第k位取反