一、哈希
①常见哈希方式
注意点:
①取模的模数,一般是质数(不容易冲突)–>搜一下,且一般先模再加再模(处理负数)(讲究顺序,例:-10的9次方先加N即10的5次方的话再模,还是负数) ②拉链法:头插法(数组模拟) ③开放寻址法(蹲坑法): 通常开数组为目标数组大小的2-3倍, find函数的功能(寻找坑位), 其返回值: 若原本存在,返回存在位置的下标,若原本不存在,返回可以插入的位置下标。 ④有关无穷大设置: 在算法竞赛中,我们常采用0x3f3f3f3f来作为无穷大。0x3f3f3f3f主要有如下好处: 0x3f3f3f3f的十进制为1061109567,和INT_MAX一个数量级,即10^9数量级,而一般场合下的数据都是小于10^9的。0x3f3f3f3f * 2 = 2122219134,无穷大相加依然不会溢出。可以使用 memset(array, 0x3f,sizeof(array))来为数组设初值为0x3f3f3f3f,因为这个数的每个字节都是0x3f 写代码时的注意事项: ①拉链法:比较值的大小的时候,比较的是e数组的里面的值,而非h数组里面的值(存放的是首地址) ②开放寻址法:注意find寻找的终止条件是&&
(1) 拉链法 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; } (2) 开放寻址法 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; }
1.1 开放寻址法
//法一:拉链法 #include<iostream> #include<cstring> using namespace std; const int N=100003; int h[N],idx,e[N],ne[N]; 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) //注意,hash表中存储的头结点的地址,比较值的大小需要用的是e数组 return true; return false; } int main() { ios::sync_with_stdio(false);cin.tie(0); int n;cin>>n; memset(h,-1,sizeof(h));//只需要给h初始化就好了,头文件cstring while(n--) { int x;string op;cin>>op>>x; if(op=="I")insert(x); else { if(find(x))cout<<"Yes"<<endl; else cout<<"No"<<endl; } } return 0; }
1.2 拉链法
//法二:开放寻址法(不考虑删除) #include<iostream> #include<cstring> using namespace std; const int N=300007,null=0x3f3f3f3f; //定义一个无穷大量 int h[N]; //函数功能:如果有x,返回x的下标;如果没有,返回第一个可以插入的位置 int find(int x) { int k=(x%N+N)%N; while(h[k]!=null&&h[k]!=x)//终止条件,找到x或者找到了空位 { k++; if(k==N)k=0;//k为0之后就重头开始 } return k; } int main() { ios::sync_with_stdio(false);cin.tie(0); int n;cin>>n; memset(h,0x3f,sizeof(h));//按字节进行初始化 while(n--) { string op;int x;cin>>op>>x; if(op=="I")h[find(x)]=x; else if(h[find(x)]==x)cout<<"Yes"<<endl; else cout<<"No"<<endl; } return 0; }
②字符串哈希841. 字符串哈希
模板
核心思想:将字符串看成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];
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];
}
题解:
#include<iostream>
using namespace std;
typedef unsigned long long ULL;
const int N=100010,P=131;
char str[N];
int p[N],h[N]; //p[2]表示P的2次方
//函数功能:获得字符串子串的哈希值
ULL get(int l,int r)
{
return h[r]-h[l-1]*p[r-l+1];
}
int main()
{
//ios::sync_with_stdio(false);cin.tie(0);
int n,m;cin>>n>>m;
scanf("%s",str+1);//细节:为了表示的统一,从下标1开始读入。
p[0]=1;
for(int i=1;i<=n;i++)//给初始化了
{
p[i]=p[i-1]*P;
h[i]=h[i-1]*P+str[i];
}
while(m--)
{
int l1,l2,r1,r2;cin>>l1>>r1>>l2>>r2;
if(get(l1,r1)==get(l2,r2)) cout<<"Yes"<<endl;
else cout<<"No"<<endl;
}
return 0;
}
二、STL
vector, 变长数组,倍增的思想 size() 返回元素个数 empty() 返回是否为空 clear() 清空 front()/back() push_back()/pop_back() begin()/end() [] 支持比较运算,按字典序 pair<int, int> first, 第一个元素 second, 第二个元素 支持比较运算,以first为第一关键字,以second为第二关键字(字典序) string,字符串 size()/length() 返回字符串长度 empty() clear() substr(起始下标,(子串长度)) 返回子串 c_str() 返回字符串所在字符数组的起始地址 queue, 队列 size() empty() push() 向队尾插入一个元素 front() 返回队头元素 back() 返回队尾元素 pop() 弹出队头元素 priority_queue, 优先队列,默认是大根堆 size() empty() push() 插入一个元素 top() 返回堆顶元素 pop() 弹出堆顶元素 定义成小根堆的方式:priority_queue<int, vector<int>, greater<int>> q; 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不支持此操作。 时间复杂度是 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() 判断是否全为0 set() 把所有位置成1 set(k, v) 将第k位变成v reset() 把所有位变成0 flip() 等价于~ flip(k) 把第k位取反