🏆🏆🏆🏆🏆🏆🏆
欢迎观看我的博客,如有问题交流,欢迎评论区留言,一定尽快回复!(大家可以去看我的专栏,是所有文章的目录)
文章字体风格:
红色文字表示:重难点✔★
蓝色文字表示:思路以及想法✔★
如果大家觉得有帮助的话,感谢大家帮忙
点赞!收藏!转发!
我的qq号是:1210931886,欢迎大家加群,一起学习,互相交流,共同进步🎉🎉✨✨
🥇🥇🥇🥇🥇🥇🥇
蓝桥杯系列,为大家提供
- 做题全集,备战蓝桥杯,就做这个系列的题即可
- 一个大概的做题规划——大家最好在此基础上提前两个月准备
备战蓝桥杯就刷这些题
第一天博客链接
第二天博客链接
备战(蓝桥杯)算法竞赛-第2天
- 3. 差分(9分钟)
- 二刷总结
- 4. 差分矩阵(12分钟)
- 六、双指针
- ★ 1. 最长连续不重复子序列(20分钟)
- 二刷总结(以空间换时间)
- 2. 数组元素的目标和(7分钟)
- 3. 判断子序列(8分钟)
- 七、二进制
- 1. 位运算算法(2分钟)
- 返回n的最后一位1:lowbit(n) = n & -n
- 一共有多少1 : while n = n ^(n & -n)或者 n -= n & -n
- 八、离散化
- ★1. 区间和(20分钟)
- ★二刷总结
- 九、区间合并
- 1. 区间合并(7分钟)
- 1. 单链表模板
- 1. 单链表(7分钟)
- 2. 双链表模板
- 1. 双链表
- 3. 模拟栈
- 1. 模拟栈
- 2. 表达式求值(20分钟)
- 4. 队列 tt = -1,hh = 0;
- 1. 模拟队列
- 5. 单调栈
- 1. 单调栈(4分钟)
- 6. 单调队列
- 1. 滑动窗口例题(10分钟)
- 7. KMP
- 1. KMP字符串(10分钟)
- 二刷体会
- ★三刷体会 ne表示算上第一个和最后一个的前缀后缀相等值
- 8. Trie树
- 1. Trie字符串统计(20分钟)
- 二刷总结
- ★2. 最大异或对
- ★二刷总结
- 9. 并查集 find merge
- 1. 合并集合(5分钟)
- 2. 连通块中点的数量(每个集合有多少个元素)
- ★3. 食物链
- 法一: x,x+n,x+n+n merge(f[x+n],f[x])
- 二刷总结
- 法二:将有关系的都存储在一个部落,用到根节点的距离表示关系
- 二刷总结
- 10. 堆 ✔ ✔
- 模板
- 1. 堆排序 ✔ ✔
- 2. 模拟堆 ✔ ✔
- 11. 哈希表 ✔ ✔
- 模板
- 例1. 模拟散列表 ✔ ✔
- 拉链法代码(链表)
- 开放寻址法代码(有空位就存,空位用null=0x3f3f3f3f)
- ★例2. 字符串哈希 ✔ ✔
- 二刷思路
3. 差分(9分钟)
二刷总结
- 由差分求s时,是要有数据连续性的,前面的改变了,要保证对应后面的也跟着
原题链接
#include<iostream>
using namespace std;
const int N = 1e5+10;
int a[N],s[N];
int main()
{
int n,m;
cin >> n >> m;
for(int i = 1; i <= n; i++) cin >> s[i];
for(int i = 1; i <= n; i++)
{
a[i] = s[i] - s[i-1];
}
while(m--)
{
int l,r,c;
cin >> l >> r >> c;
a[l] += c;
a[r+1] -= c;
}
for(int i = 1; i <= n; i++)
{
a[i] = a[i] + a[i-1];
cout << a[i] << ' ';
}
return 0;
}
4. 差分矩阵(12分钟)
原题链接
#include<iostream>
using namespace std;
const int N = 1010;
int a[N][N],s[N][N];
int main()
{
int n,m,q;
cin >> n >> m >> q;
for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++) cin >> s[i][j];
for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++)
a[i][j] = s[i][j] - s[i-1][j] - s[i][j-1] + s[i-1][j-1];
while(q--)
{
int x1,y1,x2,y2,c;
cin >> x1 >> y1 >> x2 >> y2 >> c;
a[x1][y1] += c;
a[x1][y2+1] -= c;
a[x2+1][y1] -= c;
a[x2+1][y2+1] += c;
}
for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++)
a[i][j] = a[i][j] + a[i-1][j] + a[i][j-1] - a[i-1][j-1];
for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++)
{
cout << a[i][j] << ' ';
if(j==m)
cout << endl;
}
return 0;
}
六、双指针
★ 1. 最长连续不重复子序列(20分钟)
二刷总结(以空间换时间)
原题链接
#include <iostream>
using namespace std;
const int N = 100010;
int n;
int q[N], s[N];
int main()
{
scanf("%d", &n);
for (int i = 0; i < n; i ++ ) scanf("%d", &q[i]);
int res = 0;
for (int i = 0, j = 0; i < n; i ++ )
{
s[q[i]] ++ ;
while (j < i && s[q[i]] > 1) s[q[j ++ ]] -- ;
res = max(res, i - j + 1);
}
cout << res << endl;
return 0;
}
2. 数组元素的目标和(7分钟)
原题链接
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int n, m, x;
int a[N], b[N];
int main()
{
scanf("%d%d%d", &n, &m, &x);
for (int i = 0; i < n; i ++ ) scanf("%d", &a[i]);
for (int i = 0; i < m; i ++ ) scanf("%d", &b[i]);
for (int i = 0, j = m - 1; i < n; i ++ )
{
while (j >= 0 && a[i] + b[j] > x) j -- ;
if (j >= 0 && a[i] + b[j] == x) cout << i << ' ' << j << endl;
}
return 0;
}
3. 判断子序列(8分钟)
原题链接
#include<iostream>
using namespace std;
const int N = 1e5+10;
int a[N],b[N];
int n,m;
int main()
{
cin >> n >> m;
for(int i = 1; i <= n; i++) cin >> a[i];
for(int i = 1; i <= m; i++) cin >> b[i];
int i,j;
for(i = 1, j = 1; i <= m; i++)
{
if(a[j] == b[i])
j++;
if(j==n+1)
break;
}
//cout << j << ' ' << n << endl;
if(j==n+1)
cout << "Yes";
else
cout << "No";
return 0;
}
七、二进制
最全二进制算法总结
1. 位运算算法(2分钟)
返回n的最后一位1:lowbit(n) = n & -n
一共有多少1 : while n = n ^(n & -n)或者 n -= n & -n
原题链接
#include<iostream>
using namespace std;
int main()
{
int n;
cin >> n;
while(n--)
{
int x;
cin >> x;
int res = 0;
while(x)
{
x = x ^ (x&-x);
res++;
}
cout << res << ' ';
}
return 0;
}
八、离散化
sort(alls.begin(),alls.end());
alls.erase(unique(alls.begin(),alls.end()),alls.end());
★1. 区间和(20分钟)
原题链接
★二刷总结
- 用数组存储数组边界值,如需要用二分查找
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
typedef pair<int,int> PII;
const int N = 300010;
int a[N],s[N];
vector<int> alls;
vector<PII> add;
int find(int x)
{
int l = 0,r = alls.size()-1;
while(l<r)
{
int mid = l + r >> 1;
if(alls[mid]>=x) r = mid;
else l = mid + 1;
}
return l+1;
}
int main()
{
int n,m;
cin >> n >> m;
for(int i = 0; i < n; i++)
{
int x,c;
cin >> x >> c;
add.push_back({x,c});
alls.push_back(x);
}
vector<PII> query;
for(int i = 0; i < m; i++)
{
int l,r;
cin >> l >> r;
query.push_back({l,r});
alls.push_back(l);
alls.push_back(r);
}
sort(alls.begin(),alls.end());
alls.erase(unique(alls.begin(),alls.end()),alls.end());
for(auto item : add)
{
int x = find(item.first);
a[x] += item.second;
}
for(int i = 1; i <= alls.size(); i++) s[i] = a[i] + s[i-1];
for(auto item : query)
{
cout << s[find(item.second)] - s[find(item.first)-1] << endl;
}
return 0;
}
九、区间合并
1. 区间合并(7分钟)
贪心做法
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
typedef pair<int,int> PII;
vector<PII> a;
int main()
{
int n;
cin >> n;
int l,r;
for(int i = 0; i < n; i++) cin >> l >> r, a.push_back({l,r});
sort(a.begin(),a.end());
int ed = -0x3f3f3f3f;
int cnt = 0;
for(auto x : a)
{
if(ed<x.first)
{
cnt++;
ed = max(ed,x.second);
}
else
{
ed = max(ed,x.second);
}
}
cout << cnt;
return 0;
}
数据结构
- 3. 差分(9分钟)
- 二刷总结
- 4. 差分矩阵(12分钟)
- 六、双指针
- ★ 1. 最长连续不重复子序列(20分钟)
- 二刷总结(以空间换时间)
- 2. 数组元素的目标和(7分钟)
- 3. 判断子序列(8分钟)
- 七、二进制
- 1. 位运算算法(2分钟)
- 返回n的最后一位1:lowbit(n) = n & -n
- 一共有多少1 : while n = n ^(n & -n)或者 n -= n & -n
- 八、离散化
- ★1. 区间和(20分钟)
- ★二刷总结
- 九、区间合并
- 1. 区间合并(7分钟)
- 1. 单链表模板
- 1. 单链表(7分钟)
- 2. 双链表模板
- 1. 双链表
- 3. 模拟栈
- 1. 模拟栈
- 2. 表达式求值(20分钟)
- 4. 队列 tt = -1,hh = 0;
- 1. 模拟队列
- 5. 单调栈
- 1. 单调栈(4分钟)
- 6. 单调队列
- 1. 滑动窗口例题(10分钟)
- 7. KMP
- 1. KMP字符串(10分钟)
- 二刷体会
- ★三刷体会 ne表示算上第一个和最后一个的前缀后缀相等值
- 8. Trie树
- 1. Trie字符串统计(20分钟)
- 二刷总结
- ★2. 最大异或对
- ★二刷总结
- 9. 并查集 find merge
- 1. 合并集合(5分钟)
- 2. 连通块中点的数量(每个集合有多少个元素)
- ★3. 食物链
- 法一: x,x+n,x+n+n merge(f[x+n],f[x])
- 二刷总结
- 法二:将有关系的都存储在一个部落,用到根节点的距离表示关系
- 二刷总结
- 10. 堆 ✔ ✔
- 模板
- 1. 堆排序 ✔ ✔
- 2. 模拟堆 ✔ ✔
- 11. 哈希表 ✔ ✔
- 模板
- 例1. 模拟散列表 ✔ ✔
- 拉链法代码(链表)
- 开放寻址法代码(有空位就存,空位用null=0x3f3f3f3f)
- ★例2. 字符串哈希 ✔ ✔
- 二刷思路
1. 单链表模板
1. 单链表(7分钟)
原题链接
#include <iostream>
using namespace std;
const int N = 100010;
// head 表示头结点的下标
// e[i] 表示节点i的值
// ne[i] 表示节点i的next指针是多少
// idx 存储当前已经用到了哪个点
int head, e[N], ne[N], idx;
// 初始化
void init()
{
head = -1;
idx = 0;
}
// 将x插到头结点
void add_to_head(int x)
{
e[idx] = x, ne[idx] = head, head = idx ++ ;
}
// 将x插到下标是k的点后面
void add(int k, int x)
{
e[idx] = x, ne[idx] = ne[k], ne[k] = idx ++ ;
}
// 将下标是k的点后面的点删掉
void remove(int k)
{
ne[k] = ne[ne[k]];
}
int main()
{
int m;
cin >> m;
init();
while (m -- )
{
int k, x;
char op;
cin >> op;
if (op == 'H')
{
cin >> x;
add_to_head(x);
}
else if (op == 'D')
{
cin >> k;
if (!k) head = ne[head];
else remove(k - 1);
}
else
{
cin >> k >> x;
add(k - 1, x);
}
}
for (int i = head; i != -1; i = ne[i]) cout << e[i] << ' ';
cout << endl;
return 0;
}
2. 双链表模板
void init()
{
r[0] = 1;
l[1] = 0;
idx = 2;
}
1. 双链表
原题链接
#include <iostream>
using namespace std;
const int N = 100010;
int m;
int e[N], l[N], r[N], idx;
// 在节点a的右边插入一个数x
void insert(int a, int x)
{
e[idx] = x;
l[idx] = a, r[idx] = r[a];
l[r[a]] = idx, r[a] = idx ++ ;
}
// 删除节点a
void remove(int a)
{
l[r[a]] = l[a];
r[l[a]] = r[a];
}
int main()
{
cin >> m;
// 0是左端点,1是右端点
r[0] = 1, l[1] = 0;
idx = 2;
while (m -- )
{
string op;
cin >> op;
int k, x;
if (op == "L")
{
cin >> x;
insert(0, x);
}
else if (op == "R")
{
cin >> x;
insert(l[1], x);
}
else if (op == "D")
{
cin >> k;
remove(k + 1);
}
else if (op == "IL")
{
cin >> k >> x;
insert(l[k + 1], x);
}
else
{
cin >> k >> x;
insert(k + 1, x);
}
}
for (int i = r[0]; i != 1; i = r[i]) cout << e[i] << ' ';
cout << endl;
return 0;
}
3. 模拟栈
1. 模拟栈
原题链接
#include <iostream>
using namespace std;
const int N = 100010;
int m;
int stk[N], tt;
int main()
{
cin >> m;
while (m -- )
{
string op;
int x;
cin >> op;
if (op == "push")
{
cin >> x;
stk[ ++ tt] = x;
}
else if (op == "pop") tt -- ;
else if (op == "empty") cout << (tt ? "NO" : "YES") << endl;
else cout << stk[tt] << endl;
}
return 0;
}
2. 表达式求值(20分钟)
原题链接
#include <iostream>
#include <cstring>
#include <algorithm>
#include <stack>
#include <unordered_map>
using namespace std;
stack<int> num;
stack<char> op;
void eval()
{
auto b = num.top(); num.pop();
auto a = num.top(); num.pop();
auto c = op.top(); op.pop();
int x;
if (c == '+') x = a + b;
else if (c == '-') x = a - b;
else if (c == '*') x = a * b;
else x = a / b;
num.push(x);
}
int main()
{
unordered_map<char, int> pr{{'+', 1}, {'-', 1}, {'*', 2}, {'/', 2}};
string str;
cin >> str;
for (int i = 0; i < str.size(); i ++ )
{
auto c = str[i];
if (c>='0' && c <='9')
{
int x = 0, j = i;
while (j < str.size() && isdigit(str[j]))
x = x * 10 + str[j ++ ] - '0';
i = j - 1;
num.push(x);
}
else if (c == '(') op.push(c);
else if (c == ')')
{
while (op.top() != '(') eval();
op.pop();
}
else
{
while (op.size() && op.top() != '(' && pr[op.top()] >= pr[c]) eval();
op.push(c);
}
}
while (op.size()) eval();
cout << num.top() << endl;
return 0;
}
4. 队列 tt = -1,hh = 0;
1. 模拟队列
原题链接
#include <iostream>
using namespace std;
const int N = 100010;
int m;
int q[N], hh, tt = -1;
int main()
{
cin >> m;
while (m -- )
{
string op;
int x;
cin >> op;
if (op == "push")
{
cin >> x;
q[ ++ tt] = x;
}
else if (op == "pop") hh ++ ;
else if (op == "empty") cout << (hh <= tt ? "NO" : "YES") << endl;
else cout << q[hh] << endl;
}
return 0;
}
5. 单调栈
常见模型:找出每个数左边离它最近的比它大/小的数
int tt = 0;
for (int i = 1; i <= n; i ++ )
{
while (tt && check(stk[tt], i)) tt -- ;
stk[ ++ tt] = i;
}
1. 单调栈(4分钟)
原题链接
原题链接
#include <iostream>
using namespace std;
const int N = 100010;
int stk[N], tt;
int main()
{
int n;
cin >> n;
while (n -- )
{
int x;
scanf("%d", &x);
/*读进一个数,和栈里的数比较,如果栈里的数违
规了,那么出栈*/
while (tt && stk[tt] >= x) tt -- ;
//如何栈里的数,不满足,那么输出-1
if (!tt) printf("-1 ");
//如果满足 那么输出栈
else printf("%d ", stk[tt]);
//输入该值,去比较下一个
stk[ ++ tt] = x;
}
return 0;
}
6. 单调队列
常见模型:找出滑动窗口中的最大值/最小值
int hh = 0, tt = -1;
for (int i = 0; i < n; i ++ )
{
while (hh <= tt && check_out(q[hh])) hh ++ ; // 判断队头是否滑出窗口
while (hh <= tt && check(q[tt], i)) tt -- ;
q[ ++ tt] = i;
}
1. 滑动窗口例题(10分钟)
原题链接
原题链接
#include<iostream>
#include<deque>
using namespace std;
const int N = 1e6+10;
int a[N];
deque<int> q;
int main()
{
int n,k;
cin >> n >> k;
for(int i = 1; i <= n; i++) cin >> a[i];
for(int i = 1; i <= n; i++)
{
if(q.size() && q.front()+k==i)q.pop_front();
while(q.size() && a[q.back()]>=a[i])q.pop_back();
q.push_back(i);
if(i>=k)
cout << a[q.front()] << ' ';
}
cout << endl;
while(q.size())q.pop_back();
for(int i = 1; i <= n; i++)
{
if(q.size() && q.front()+k==i)q.pop_front();
while(q.size() && a[q.back()]<=a[i])q.pop_back();
q.push_back(i);
if(i>=k)
cout << a[q.front()] << ' ';
}
return 0;
}
7. KMP
// s[]是长文本,p[]是模式串,n是s的长度,m是p的长度
求模式串的Next数组:
for (int i = 2, j = 0; i <= m; i ++ )
{
while (j && p[i] != p[j + 1]) j = ne[j];
if (p[i] == p[j + 1]) j ++ ;
ne[i] = j;
}
// 匹配
for (int i = 1, j = 0; i <= n; i ++ )
{
while (j && s[i] != p[j + 1]) j = ne[j];
if (s[i] == p[j + 1]) j ++ ;
if (j == m)
{
j = ne[j];
// 匹配成功后的逻辑
}
}
1. KMP字符串(10分钟)
原题链接
二刷体会
- next数组存储的是,但j不匹配了,j调整到匹配位置,然后j+1和i去比较
- 所以j就需要 = i - 1
- 当角标从0开始 j 就得=-1,角标从1开始,j = 0
★三刷体会 ne表示算上第一个和最后一个的前缀后缀相等值
#include <iostream>
using namespace std;
const int N = 100010, M = 1000010;
int n, m;
int ne[N];
char s[M], p[N];
int main()
{
cin >> n >> p + 1 >> m >> s + 1;
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 ++ ;
ne[i] = j;
}
for (int i = 1, j = 0; i <= m; i ++ )
{
while (j && s[i] != p[j + 1]) j = ne[j];
if (s[i] == p[j + 1]) j ++ ;
if (j == n)
{
printf("%d ", i - n);
j = ne[j];
}
}
return 0;
}
8. Trie树
1. Trie字符串统计(20分钟)
二刷总结
一行只存储一个数据
原题链接
#include<iostream>
using namespace std;
const int N = 1e5+10;
int a[26][N],idx;
int cnt[N];
int main()
{
int n;
cin >> n;
while(n--)
{
char ch;
cin >> ch;
if(ch == 'I')
{
string s;
cin >> s;
int p = 0;
for(int i = 0; i < s.size();i++)
{
if(a[s[i] - 'a'][p])
{
p = a[s[i] - 'a'][p];
}
else
{
a[s[i] - 'a'][p] = ++idx;
p = a[s[i] - 'a'][p];
}
}
cnt[p]++;
}
else
{
string s;
cin >> s;
int p = 0;
for(int i = 0; i < s.size(); i++)
{
if(a[s[i]-'a'][p])
{
p = a[s[i]-'a'][p];
}
else
{
cout << 0 << endl;
break;
}
if(i==s.size()-1)
{
cout << cnt[p] << endl;
}
}
}
}
return 0;
}
★2. 最大异或对
原题链接
★二刷总结
正常思路:
- 两个循环遍历,时间复杂度(N2)
for(int i = 0; i < n; i++)
{
for(int j = 0; j < n; j++)
{
res = max(res,a[i]^a[j]);
}
}
- 优化:创建一个Trie树,针对一个数,去遍历Trie树
(怎么想到用Trie树去优化呢?Trie树可以存储组合性质的信息)
#include<iostream>
#include<algorithm>
using namespace std;
int const N=100010,M=31*N;
int n;
int a[N];
int son[M][2],idx;
//M代表一个数字串二进制可以到多长
void insert(int x)
{
int p=0; //根节点
for(int i=30;i>=0;i--)
{
int u=x>>i&1; /取X的第i位的二进制数是什么 x>>k&1(前面的模板)
if(!son[p][u]) son[p][u]=++idx; ///如果插入中发现没有该子节点,开出这条路
p=son[p][u]; //指针指向下一层
}
}
int search(int x)
{
int p=0;int res=0;
for(int i=30;i>=0;i--)
{
///从最大位开始找
int u=x>>i&1;
if(son[p][!u]) 如果当前层有对应的不相同的数
{ ///p指针就指到不同数的地址
p=son[p][!u];
res=res*2+1;
///*2相当左移一位 然后如果找到对应位上不同的数res+1 例如 001
} /// 010
else --->011 //刚开始找0的时候是一样的所以+0 到了0和1的时候原来0右移一位,判断当前位是同还是异,同+0,异+1
{
p=son[p][u];
res=res*2+0;
}
}
return res;
}
int main(void)
{
cin.tie(0);
cin>>n;
idx=0;
for(int i=0;i<n;i++)
{
cin>>a[i];
insert(a[i]);
}
int res=0;
for(int i=0;i<n;i++)
{
res=max(res,search(a[i])); ///search(a[i])查找的是a[i]值的最大与或值
}
cout<<res<<endl;
}
9. 并查集 find merge
1. 合并集合(5分钟)
原题链接
原题链接
#include<iostream>
using namespace std;
const int N=100010;
int p[N];//定义多个集合
int find(int x)
{
if(p[x]!=x) p[x]=find(p[x]);
/*
经上述可以发现,每个集合中只有祖宗节点的p[x]值等于他自己,即:
p[x]=x;
*/
return p[x];
//找到了便返回祖宗节点的值
}
int main()
{
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) p[i]=i;
while(m--)
{
char op[2];
int a,b;
scanf("%s%d%d",op,&a,&b);
if(*op=='M') p[find(a)]=find(b);//集合合并操作
else
if(find(a)==find(b))
//如果祖宗节点一样,就输出yes
printf("Yes\n");
else
printf("No\n");
}
return 0;
}
2. 连通块中点的数量(每个集合有多少个元素)
原题链接
#include <iostream>
using namespace std;
const int N = 100010;
int n, m;
int p[N], cnt[N];
int find(int x)
{
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i ++ )
{
p[i] = i;
cnt[i] = 1;
}
while (m -- )
{
string op;
int a, b;
cin >> op;
if (op == "C")
{
cin >> a >> b;
a = find(a), b = find(b);
if (a != b)
{
p[a] = b;
cnt[b] += cnt[a];
}
}
else if (op == "Q1")
{
cin >> a >> b;
if (find(a) == find(b)) puts("Yes");
else puts("No");
}
else
{
cin >> a;
cout << cnt[find(a)] << endl;
}
}
return 0;
}
★3. 食物链
原题链接
法一: x,x+n,x+n+n merge(f[x+n],f[x])
思路:
- 因为有三种物种,A吃B,B吃C,C吃A
- 如果我们用一个数组存储,那么比如1吃2,那么我们让2的角标处的值标记成1,如果3吃2,那怎么标记?一个数组指定标记不过来。
- 那么我们想用三个数组存储,其实也存储不过来,因为角标就那么几个,
- 最好的方法就是,用x,x+n,x+n+n来表示
比如1吃2,那么就可能有三种情况,
A类中的1吃B类的2 : fa[1] = fa[2+n+n]
B类中的1吃C类的2 : fa[1+n] = fa[2]
C类中的1中A类的2 : fa[1+n+n] = fa[2+n];
这样的话,就会有3*n个角标,就可以充分表达
A中的1吃B中的2(B中的2用2+n表示)
这样的话就不会出现数字冲突
A吃B
则让f[A] = B
二刷总结
a吃b
f[a+n] = f[b]
或者
f[b+n] = f[a]
/*
*/
#include <bits/stdc++.h>
using namespace std;
int fa[200000];
int n,m,k,x,y,ans;
int get(int x)
{
if(x==fa[x])
return x;
return fa[x]=get(fa[x]);
}
void merge(int x,int y)
{
fa[get(x)]=get(y);
}
int main()
{
cin>>n>>m;
for(int i=1;i<=3*n;i++)
fa[i]=i;
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&k,&x,&y);
if(x>n || y>n)
ans++;
else if(k==1)
{
if(get(x)==get(y+n) || get(x)==get(y+n+n)) //如果x,y是同类,但是x是y的捕食中的动物,或者x是y天敌中的动物,那么错误.
ans++;
else
{
merge(x,y);
merge(x+n,y+n);
merge(x+n+n,y+n+n);
}
}
else
{
if(x==y || get(x)==get(y) || get(x)==get(y+n)) //x就是y,或者他们是同类,再或者是y的同类中有x
ans++;//都是假话
else
{
merge(x,y+n+n);//y的捕食域加入x
merge(x+n,y);//x的天敌域加入y
merge(x+n+n,y+n);//x的捕食域是y的同类域.
}
}
}
cout<<ans<<endl;
}
法二:将有关系的都存储在一个部落,用到根节点的距离表示关系
二刷总结
- X吃Y 让x的祖宗等于y的祖宗 或者 y的祖宗等于x的祖宗都可以
- find查找函数中,具有压缩路径的作用,所以在写的时候,先找到此根节点,然后依次压缩,如下代码
int find(int x)
{
if (p[x] != x)
{
int t = find(p[x]);
d[x] += d[p[x]];
p[x] = t;
}
return p[x];
}
不可以如下代码
int find(int x)
{
if (p[x] != x)
{
d[x] += d[p[x]];
return p[x] = find(p[x]);
}
return p[x];
}
- 在查询合并过程中,比如查询x的父节点,应该用一个变量记录下来,不能多次find,不然找不到x的原来父节点了
- 初始化 p[i] = i; d[i] = 0;
- 合并的时候,画图即可明白彼此的距离
具体过程如下:
#include <iostream>
using namespace std;
const int N = 50010;
int n, m;
int p[N], d[N];
int find(int x)
{
if (p[x] != x)
{
int t = find(p[x]);
d[x] += d[p[x]];
p[x] = t;
}
return p[x];
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i ++ ) p[i] = i;
int res = 0;
while (m -- )
{
int t, x, y;
scanf("%d%d%d", &t, &x, &y);
if (x > n || y > n) res ++ ;
else
{
int px = find(x), py = find(y);
if (t == 1)
{
if (px == py && (d[x] - d[y]) % 3) res ++ ;
else if (px != py)
{
p[px] = py;
d[px] = d[y] - d[x];
}
}
else
{
if (px == py && (d[x] - d[y] - 1) % 3) res ++ ;
else if (px != py)
{
p[px] = py;
d[px] = d[y] + 1 - d[x];
}
}
}
}
printf("%d\n", res);
return 0;
}
10. 堆 ✔ ✔
模板
// h[N]存储堆中的值, h[1]是堆顶,x的左儿子是2x, 右儿子是2x + 1
// ph[k]存储第k个插入的点在堆中的位置
// hp[k]存储堆中下标是k的点是第几个插入的
int h[N], ph[N], hp[N], size;
// 交换两个点,及其映射关系
void heap_swap(int a, int b)
{
swap(ph[hp[a]],ph[hp[b]]);
swap(hp[a], hp[b]);
swap(h[a], h[b]);
}
void down(int u)
{
int t = u;
if (u * 2 <= size && h[u * 2] < h[t]) t = u * 2;
if (u * 2 + 1 <= size && h[u * 2 + 1] < h[t]) t = u * 2 + 1;
if (u != t)
{
heap_swap(u, t);
down(t);
}
}
void up(int u)
{
while (u / 2 && h[u] < h[u / 2])
{
heap_swap(u, u / 2);
u >>= 1;
}
}
// O(n)建堆
for (int i = n / 2; i; i -- ) down(i);
- 向下调整
法一:如果孩子节点 小于当前节点 那么交换 然后递归
void down(int u)
{
int t = u;
if (u * 2 <= size && h[u * 2] < h[t]) t = u * 2;
if (u * 2 + 1 <= size && h[u * 2 + 1] < h[t]) t = u * 2 + 1;
if (u != t)
{
heap_swap(u, t);
down(t);
}
}
法二:parent = child * 2 直到 超出范围
public void createHeap() {
for (int parent = (usedSize-1-1) / 2; parent >= 0 ; parent--) {
shiftDown(parent,usedSize);
}
}
/**
* 实现 向下调整
* @param parent 每棵子树的根节点的下标
* @param len 每棵子树的结束位置
*/
private void shiftDown(int parent ,int len) {
int child = 2 * parent + 1;
//最起码是有左孩子
while (child < len) {
//判断 左孩子 和 右孩子 谁最大,前提是 必须有 右孩子
if(child+1 < len && elem[child] < elem[child+1]) {
child++;//此时 保存了最大值的下标
}
if(elem[child] > elem[parent]) {
swap(elem,child,parent);
parent = child;
child = 2*parent+1;
}else {
break;
}
}
}
private void swap(int[] array,int i,int j) {
int tmp = array[i];
array[i] = array[j];
array[j] = tmp;
}
堆的操作
- 删除最值
- 因为最小值,位于数组的最前端,所以我们可以让最前端等于最后端的值,(此时堆的最小值已经没了)
- 但是呢,堆里出现了两个最前端的值,所以需要再–sz删除最后段的值(不要怕,最后段的值,已经保存到最前端的结点上,)
- 最后把最顶端的点 用down
- 修改值(改,先down,后up)
孩子和父亲的关系
数组建堆
1. 堆排序 ✔ ✔
原题链接
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 100010;
int n, m;
int h[N], cnt;
void down(int u)
{
int t = u;
if (u * 2 <= cnt && h[u * 2] < h[t]) t = u * 2;
if (u * 2 + 1 <= cnt && h[u * 2 + 1] < h[t]) t = u * 2 + 1;
if (u != t)
{
swap(h[u], h[t]);
down(t);
}
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i ++ ) scanf("%d", &h[i]);
cnt = n;
for (int i = n / 2; i; i -- ) down(i);
while (m -- )
{
printf("%d ", h[1]);
h[1] = h[cnt -- ];
down(1);
}
puts("");
return 0;
}
2. 模拟堆 ✔ ✔
原题链接
#include <iostream>
#include <algorithm>
#include <string.h>
using namespace std;
const int N = 100010;
int h[N], ph[N], hp[N], cnt;
void heap_swap(int a, int b)
{
swap(ph[hp[a]],ph[hp[b]]);
swap(hp[a], hp[b]);
swap(h[a], h[b]);
}
void down(int u)
{
int t = u;
if (u * 2 <= cnt && h[u * 2] < h[t]) t = u * 2;
if (u * 2 + 1 <= cnt && h[u * 2 + 1] < h[t]) t = u * 2 + 1;
if (u != t)
{
heap_swap(u, t);
down(t);
}
}
void up(int u)
{
while (u / 2 && h[u] < h[u / 2])
{
heap_swap(u, u / 2);
u >>= 1;
}
}
int main()
{
int n, m = 0;
scanf("%d", &n);
while (n -- )
{
char op[5];
int k, x;
scanf("%s", op);
if (!strcmp(op, "I"))
{
scanf("%d", &x);
cnt ++ ;
m ++ ;
ph[m] = cnt, hp[cnt] = m;
h[cnt] = x;
up(cnt);
}
else if (!strcmp(op, "PM")) printf("%d\n", h[1]);
else if (!strcmp(op, "DM"))
{
heap_swap(1, cnt);
cnt -- ;
down(1);
}
else if (!strcmp(op, "D"))
{
scanf("%d", &k);
k = ph[k];
heap_swap(k, cnt);
cnt -- ;
up(k);
down(k);
}
else
{
scanf("%d%d", &k, &x);
k = ph[k];
h[k] = x;
up(k);
down(k);
}
}
return 0;
}
11. 哈希表 ✔ ✔
模板
(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. 模拟散列表 ✔ ✔
原题链接
拉链法代码(链表)
- N取大于范围的第一个质数
- k = (x % N + N) % N; (%N 为了避免超级大的值 + N 为了避免出现负数 再%N为了正数+N超出范围)
- memset(h, -1, sizeof h);
#include <cstring>
#include <iostream>
using namespace std;
const int N = 1e5 + 3; // 取大于1e5的第一个质数,取质数冲突的概率最小 可以百度
//* 开一个槽 h
int h[N], e[N], ne[N], idx; //邻接表
void insert(int x) {
// c++中如果是负数 那他取模也是负的 所以 加N 再 %N 就一定是一个正数
int k = (x % N + N) % N;
e[idx] = x;
ne[idx] = h[k];
h[k] = idx++;
}
bool find(int x) {
//用上面同样的 Hash函数 讲x映射到 从 0-1e5 之间的数
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 n;
int main() {
cin >> n;
memset(h, -1, sizeof h); //将槽先清空 空指针一般用 -1 来表示
while (n--) {
string op;
int x;
cin >> op >> x;
if (op == "I") {
insert(x);
} else {
if (find(x)) {
puts("Yes");
} else {
puts("No");
}
}
}
return 0;
}
开放寻址法代码(有空位就存,空位用null=0x3f3f3f3f)
- 大于2倍的第一个质数
#include <cstring>
#include <iostream>
using namespace std;
//开放寻址法一般开 数据范围的 2~3倍, 这样大概率就没有冲突了
const int N = 2e5 + 3; //大于数据范围的第一个质数
const int null = 0x3f3f3f3f; //规定空指针为 null 0x3f3f3f3f
int h[N];
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; //如果这个位置是空的, 则返回的是他应该存储的位置
}
int n;
int main() {
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)] == null) {
puts("No");
} else {
puts("Yes");
}
}
}
return 0;
}
★例2. 字符串哈希 ✔ ✔
原题链接
二刷思路
- 把一段字符串转成一段数字,便可以直接比较相等
- 怎么转?进制大于26
- 样例没找全 p[r-l+1] 这个点没找到
#include<iostream>
#include<cstdio>
#include<string>
using namespace std;
typedef unsigned long long ULL;
const int N = 1e5+5,P = 131;//131 13331
ULL h[N],p[N];
// h[i]前i个字符的hash值
// 字符串变成一个p进制数字,体现了字符+顺序,需要确保不同的字符串对应不同的数字
// P = 131 或 13331 Q=2^64,在99%的情况下不会出现冲突
// 使用场景: 两个字符串的子串是否相同
ULL query(int l,int r){
return h[r] - h[l-1]*p[r-l+1];
}
int main(){
int n,m;
cin>>n>>m;
string x;
cin>>x;
//字符串从1开始编号,h[1]为前一个字符的哈希值
p[0] = 1;
h[0] = 0;
for(int i=0;i<n;i++){
p[i+1] = p[i]*P;
h[i+1] = h[i]*P +x[i]; //前缀和求整个字符串的哈希值
}
while(m--){
int l1,r1,l2,r2;
cin>>l1>>r1>>l2>>r2;
if(query(l1,r1) == query(l2,r2)) printf("Yes\n");
else printf("No\n");
}
return 0;
}