acwing菜鸟闯关记(二)
单链表
struct Node
{
int date;
struct Node * next
}
new Node()
单链表源数据结构如代码。
运用数组模拟单链表速度更快。
void init(){//初始化
head = -1;
idx = 0;
}
void add_to_head(int val){//加到头节点后
e[idx] = val;
ne[idx] = head;
head = idx;
idx ++;
}
void add(int val,int k){//加到k节点后
e[idx] = val;
ne[idx] = ne[k];
ne[k] = idx;
idx++;
}
void remove(int k){//移除k节点
ne[k] = ne[ne[k]];
}
思路:e[]表示值,ne[]表示下一节点。
双链表
void init(){
//0左端点,1右端点
r[0] = 1;
l[1] = 0;
idx = 2;
}
//下标k右边插入x
void add(int k ,int x){
e[idx] = x;
r[idx] = r[k];
l[idx] = k;
l[r[k]] = idx;//先后顺序
r[k] = idx;
idx++;
}
//左边插入调用 add(l[k],x)
//删除第k个
void remove(int k){
r[l[k]] = r[k];
l[r[k]] = l[k];
}
模拟栈
int m;
const int N = 100010;
int stk[N],tt = 0;
//插入
stk[++ tt] == x;
//弹出
tt --;
//判断是否为空
if(tt >0) not empty
else empty
//栈顶
cout<<stl[tt];
栈用数组,最高位就是栈顶。数组覆盖与数组往上加。
模拟队列
/*
* 队尾插入元素
* 队头hh,队尾tt
*/
int q[N],hh,tt = -1;
//进队列
q[++tt] = x;
//弹出
hh++;
//判空
if(hh <= tt) not empty
else empty
//队头
cout<<q[hh];
单调栈
栈中元素按照递增或者递减顺序排列。每个元素只会进栈一次,进栈时会把破坏单调性的栈内元素弹出。
while(tt && stk[tt]>=x) tt--;
if (tt) cout<<stk[tt]<<" ";
else cout<<"-1"<<" ";
stk[++tt] = x;
单调队列
滑动窗口,维护一个双向队列,遍历序列,仅当一个元素可能成为某个区间最值时才保留它。
int hh = 0,tt = -1;
for (int i = 0; i < n; ++i) {
if (hh<=tt&&q[hh]<i-k+1) hh++;
while(hh<=tt && a[q[tt]] >= a[i]) tt--;
q[++tt] = i;
if (i>=k-1) cout<<a[q[hh]]<<" ";
}
KMP
//构建next数组
//i是用来扫描后缀的,j是用来扫描前缀
for(int i = 2,j = 0;i <= n;i++)
{
while(j && p[i] != p[j + 1])
j = ne[j];//当前后缀不匹配时,就查表,往前跳,直到匹配为止
if(p[i] == p[j + 1])
j++;//匹配j就加1
ne[i] = j;//存下来
}
//寻找匹配值
for(int i = 1,j = 0;i <= m;i++)
{
// 当j为0或者不匹配时,就往前跳,而跳的位置需要查上一位的next数组
while(j && s[i] != p[j + 1])
j = ne[j];
//跳出while循环有两种,如果它们匹配的话,j就加1,再继续匹配
if(s[i] == p[j + 1])
j++;
if(j == n)
{
printf("%d ",i - n);//说明匹配成功了,输出下标
j = ne[j];
//注意一定要执行这一步,因为不知道字符串后面还有没有可以匹配的,否则就会不动了
}
}
Trie树
const int N = 1000010;
int son[N][26],cnt[N],idx;//son子节点,最多连26条边
// cnt为以当前这个点为结尾的单词有多少个,
// idx 存当前用到了哪个下标
// 下标为0的点,又是根节点又是空节点
//全局变量不定义就是0
char str[N];
void insert(char str[])
{
int p = 0;//p就是一个指针,刚开始指向root
//由于字符串结尾是"\0",可以通过str[i]来判断是否到字符串末尾
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]++;//以这个字符结束的单词次数加1,即给单词做标记
}
int query(char str[])
{
int p = 0;//同样的,刚开始指向root
for(int i = 0;str[i];i++)// 判断是否到字符串末尾
{
int u = str[i] - 'a';// 得到要查询的节点
//遍历树,如果没有此节点说明单词不存在
if(!son[p][u])
return 0;
p = son[p][u];//有这个节点的话,就令p指向这个节点,然后看看这个节点下面是否有下一个元素的节点
}
return cnt[p];//返回单词出现次数
}
最大异或对
^为位异或
void insert(int x){
int p = 0;
for (int i = 30; i>=0; i--) {//i>=0 等价于~i
int &s = son[p][x>>i&1];//取X的第i位的二进制数是什么 x>>k&1(前面的模板)
if (!s) s = ++idx;
p = s;
}
}
int query(int x){
int res = 0,p = 0;
for (int i = 30; i >=0 ; i--) {
int s = x >> i & 1;
if (son[p][!s]){
res+= 1<<i;//第i位变1,也就是加上高位1;res =res*2+1
p = son[p][!s];
}
else{
// res += 0 <<i;//可省略 res = res*2+0
p = son[p][s];
}
}
return res;
}
并查集
1、合并两个集合-----》father接father
2、查询两个元素是否在一个集合-----》father是否一样
时间复杂度近似O(1)
const int N = 100010;
int n,m;
int p[N],d[N];//d[N]为到根节点距离
//查询x的祖宗节点 + 路径压缩
int find(int x){
if (p[x] != x){
int t = find(p[x]);
d[x] += d[p[x]];
p[x] = t;
}
return p[x];
}
堆排序
是最高效的优先级队列。堆通常是一个可以被看做一棵完全二叉树的数组对象。
手写堆的的常用操作有几个
- 插入一个数
- 求集合当中的最小值
- 删除最小值
- 删除任意一个元素
- 修改任意一个元素
堆是用一个一维数组存储的,根节点存到下标为1的位置,节点的左儿子存到下标为2x的位置;右儿子存到下标为2x+1的位置。
小根堆性质:父节点比它的左右儿子的值小。
down操作
void down(int x){//注意变量 不能改变,变了就坏了
int t = x;
if (x * 2 <=hsize && h[x*2] <h[t] ) t = x*2;
if (x * 2+1 <=hsize && h[x*2+1] <h[t] ) t = x*2+1;
if (x!=t){
swap(h[x],h[t]);
down(t);
}
}
up操作
void up(int x){//注意变量 不能改变,变了就坏了
while (x/2 && h[x/2]>h[x]){
swap(x/2,x);
x/=2;
}
}
heap[size]为数组最后一个元素;
heap[1]为根节点
哈希
哈希表最主要的作用把一个比较庞大的空间/值域映射到比较小的空间。
第一种为开放寻址法。
数组最好取质数。
//查找坑位
int query(int x){
int k = (x%N+N)%N;
while(h[k]!=null &&h[k]!=x){
k++;
if (k == N) k = 0;
}
return k;
}
if (op[0] == 'I'){//插入
int k = query(x);
h[k] = x;
}
else{//查找
int k = query(x);
if (h[k]!=null) puts("Yes");
else puts("No");
}
第二种为拉链法。
void insert(int x){//插入,单链表
int k = (x%N+N)%N;
e[idx] = x;
ne[idx] = h[k];
h[k] = idx;
idx++;
}
bool query(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;
}
memset(h,-1,sizeof h);//初始化
字符串哈希
本质上是前缀和与权值相加结合。
P一般取131或13331,数组取质数
//前缀和求差
int n,m;
char str[N];
ULL h[N],p[N];
ULL get(int l,int r){
return h[r] - h[l-1]*p[r-l+1];
}
//构造前缀和
for (int i = 1; i <= n; ++i) {
p[i] = p[i-1] * P;
h[i] = h[i-1]*P + str[i];
}
加油!拼搏百天!
HEBUT 作者:陈冰yunshangjin
邮箱:suchenbin@163.com 可以发邮件进行交流