递归
AcWing 1497. 树的遍历(每日一题)
思路:
给定一棵树的后序和中序遍历序列可以唯一确定一棵二叉树。
对于后序遍历序列,根节点一定出现在最后面,这样可以唯一确定根节点。
对于中序遍历序列,根节点一定出现在左右子树之间,结合后序遍历序列可以确定左右子树。
递归地执行上述过程,可以构造出一棵二叉树,最后再 b f s bfs bfs 一下输出中序遍历序列。
代码:
#include <iostream>
using namespace std;
const int N = 35;
// p存储根节点在中序遍历中的位置
int l[N], r[N], p[N];
int back[N], mid[N];
int q[N];
int n;
// 后序 起点 终点 中序 起点 终点
int generate(int al, int ar, int bl, int br)
{
if (al > ar) return 0;
int root = back[ar];
int k = p[root];
// ar - al = k - 1 - bl ar = al + k - 1 - bl
l[root] = generate(al, al + k - 1 - bl, bl, k - 1);
// bl - k - 1 = ar - 1 - al al = ar - 1 - bl + k + 1 = ar -bl + k
r[root] = generate(ar - br + k, ar - 1, k + 1, br);
return root;
}
void bfs(int root)
{
int hh = 0, tt = -1;
q[++tt] = root;
while (hh <= tt)
{
int t = q[hh++];
printf("%d ", t);
if (l[t]) q[++tt] = l[t];
if (r[t]) q[++tt] = r[t];
}
}
int main()
{
cin >> n;
for (int i = 1; i <= n; i++) cin >> back[i];
for (int i = 1; i <= n; i++)
{
cin >> mid[i];
p[mid[i]] = i;
}
int root = generate(1, n, 1, n);
bfs(root);
return 0;
}
并查集
AcWing 1249. 亲戚(每日一题)
思路:
并查集可以高效判断两个节点是否在一个集合中。构造并查集要实现初始化,查询以及合并操作。
(1)朴素并查集:
int p[N]; //存储每个点的祖宗节点
// 返回x的祖宗节点
int find(int x)
{
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
// 初始化,假定节点编号是1~n
for (int i = 1; i <= n; i ++ ) p[i] = i;
// 合并a和b所在的两个集合:
p[find(a)] = find(b);
代码:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
/**
* @Description
* @Author: PrinceHan
* @CreateTime: 2023/2/25 16:37
*/
public class Main {
static final int N = 20005;
static int n, m;
static int[] p = new int[N];
public static void main(String[] args) throws IOException {
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
String[] nk = in.readLine().split(" ");
n = Integer.parseInt(nk[0]);
m = Integer.parseInt(nk[1]);
for (int i = 1; i <= n; i++) p[i] = i;
while (m-- != 0) {
String[] s = in.readLine().split(" ");
int a = Integer.parseInt(s[0]), b = Integer.parseInt(s[1]);
int aa = find(a), bb = find(b);
if (aa == bb) continue;
p[aa] = bb;
}
int q = Integer.parseInt(in.readLine().split(" ")[0]);
while (q-- != 0) {
String[] s = in.readLine().split(" ");
int c = Integer.parseInt(s[0]), d = Integer.parseInt(s[1]);
if (find(c) == find(d)) out.println("Yes");
else out.println("No");
}
out.flush();
}
public static int find(int x) {
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
}
AcWing 836. 合并集合(算法基础课)
代码:
#include <iostream>
using namespace std;
const int N = 100005;
int p[N];
int n, m;
// 查询祖宗节点
int find(int x)
{
// 路径压缩
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
// 合并集合
void merge(int a, int b)
{
// 同一个祖先 不需要合并
if (find(a) == find(b)) return;
// a的祖先的父亲是b的祖先 操作可以互换
p[find(a)] = find(b);
}
int main()
{
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[0] == 'M') merge(a, b);
else cout << (find(a) == find(b) ? "Yes" : "No") << endl;
}
}
AcWing 837. 连通块中点的数量(算法基础课)
思路:
需要维护一个数组,用来记录集合中的节点数量
(2)维护size的并查集:
int p[N], size[N];
//p[]存储每个点的祖宗节点, size[]只有祖宗节点的有意义,表示祖宗节点所在集合中的点的数量
// 返回x的祖宗节点
int find(int x)
{
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
// 初始化,假定节点编号是1~n
for (int i = 1; i <= n; i ++ )
{
p[i] = i;
size[i] = 1;
}
// 合并a和b所在的两个集合:
size[find(b)] += size[find(a)];
p[find(a)] = find(b);
代码:
#include <iostream>
using namespace std;
const int N = 100005;
int p[N], cnt[N];
int n, m;
// 查询祖宗节点
int find(int x)
{
// 路径压缩
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
// 合并集合
void merge(int a, int b)
{
// 同一个祖先 不需要合并
if (find(a) == find(b)) return;
// a的祖先的祖先是b的祖先 操作可以互换
cnt[find(b)] += cnt[find(a)];
p[find(a)] = find(b);
}
int main()
{
scanf("%d%d", &n, &m);
// 初始化并查集
for (int i = 1; i <= n; i++) p[i] = i, cnt[i] = 1;
while (m--)
{
char op[5];
int a, b;
scanf("%s", op);
if (op[0] == 'C')
{
scanf("%d%d", &a, &b);
merge(a, b);
}
else if (op[1] == '1')
{
scanf("%d%d", &a, &b);
printf(find(a) == find(b) ? "Yes\n" : "No\n");
}
else
{
scanf("%d", &a);
printf("%d\n", cnt[find(a)]);
}
}
return 0;
}
哈希表
AcWing 2058. 笨拙的手指(每日一题)
思路:
对于每个二进制表示,可以枚举每一个错误的位置,要注意一个特殊情况:如果一个二进制表示的长度大于1并且含有前导0,则需要跳过这种情况。注意一切操作都是要先修改错误位置的元素,再进行判断。对符合条件的二进制序列,转化为十进制数加入哈希表中。通过上述步骤将三进制的序列转化为十进制数,如果在哈希表中存在,则该数就是所求的答案。
代码:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.HashSet;
/**
* @Description
* @Author: PrinceHan
* @CreateTime: 2023/3/7 21:06
*/
public class Main {
public static int base(String a, int b) {
int res = 0;
for (int i = 0; i < a.length(); i++) {
res = res * b + a.charAt(i) - '0';
}
return res;
}
public static void main(String[] args) throws IOException {
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
String x = in.readLine();
String y = in.readLine();
HashSet<Integer> res = new HashSet<>();
for (int i = 0; i < x.length(); i++) {
char[] s = x.toCharArray();
s[i] ^= 1;
if (s.length > 1 && s[0] == '0') continue;
res.add(base(new String(s), 2));
}
for (int i = 0; i < y.length(); i++) {
for (int j = 0; j < 3; j++) {
char[] s = y.toCharArray();
if (s[i] - '0' == j) continue;
s[i] = (char) (j + '0');
if (s.length> 1 && s[0] == '0') continue;
int n = base(new String(s), 3);
if (res.contains(n)) {
out.println(n);
}
}
}
out.flush();
}
}
AcWing 840. 模拟散列表(算法基础课)
拉链法
#include<iostream>
#include <cstring>
using namespace std;
const int N = 100003;
int h[N], e[N], ne[N], idx;
int 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) return true;
return false;
}
int main()
{
scanf("%d", &n);
memset(h, -1, sizeof h);
char op[2];
int x;
while (n--)
{
scanf("%s%d", op, &x);
if (op[0] == 'I') insert(x);
else printf(find(x) ? "Yes\n" : "No\n");
}
return 0;
}
开放寻址法,范围2~3倍
#include<iostream>
#include <cstring>
using namespace std;
const int N = 200003, null = 0x3f3f3f3f; // 范围2~3倍 质数
int h[N];
int n;
// 如果x在哈希表中,返回x的下标;如果x不在哈希表中,返回x应该插入的位置
int find(int x)
{
int k = (x % N + N) % N;
while (h[k] != null && h[k] != x)
{
k++;
if (k == N) k = 0;
}
return k;
}
int main()
{
scanf("%d", &n);
// memset 按字节赋值 后八位
memset(h, 0x3f, sizeof h);
char op[2];
int x;
while (n--)
{
scanf("%s%d", op, &x);
int k = find(x);
if (op[0] == 'I') h[k] = x;
else printf(h[k] != null ? "Yes\n" : "No\n");
}
return 0;
}
AcWing 841. 字符串哈希(算法基础课)
核心思想: 将字符串看成P进制数,P的经验值是131或13331,取这两个值的冲突概率低
小技巧: 取模的数用
2
64
2^{64}
264,这样直接用 unsigned long long
存储,溢出的结果就是取模的结果
注意: 字符串从1的位置开始存。前
l
l
l 位字符串的哈希值是
h
[
l
]
h[l]
h[l],前
r
r
r 位字符串的哈希值是
h
[
r
]
h[r]
h[r],则
l
∼
r
l \sim r
l∼r 之间字符串(包含端点)的哈希值可以表示为:
h
[
l
∼
r
]
=
h
[
1
∼
r
]
−
h
[
1
∼
l
−
1
]
∗
P
r
−
l
+
1
\begin{aligned} h[l \sim r] &= h[1 \sim r] - h[1 \sim l - 1] * P ^{r - l + 1} \end{aligned}
h[l∼r]=h[1∼r]−h[1∼l−1]∗Pr−l+1
模板
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 = 100005, P = 131;
ull h[N], p[N];
char str[N];
ull get(int l, int r)
{
return h[r] - h[l - 1] * p[r - l + 1];
}
int main()
{
int n, m;
scanf("%d%d%s", &n, &m, str + 1);
p[0] = 1;
for (int i = 1; str[i]; i++)
{
h[i] = h[i - 1] * P + str[i];
p[i] = p[i - 1] * P;
}
while (m--)
{
int l1, r1, l2, r2;
scanf("%d%d%d%d", &l1, &r1, &l2, &r2);
if (get(l1, r1) == get(l2, r2)) printf("Yes\n");
else printf("No\n");
}
return 0;
}
KMP
AcWing 141. 周期(每日一题)
思路:
本质上是使用
K
M
P
KMP
KMP 算法 求出最短循环节。
对一段字符序列
s
[
1
∼
i
]
s[1 \sim i]
s[1∼i],如果
i
−
n
e
[
i
]
m
o
d
n
e
[
i
]
=
0
i - ne[i] \bmod ne[i] = 0
i−ne[i]modne[i]=0,则
s
[
1
∼
i
]
s[1 \sim i]
s[1∼i] 是字符序列
s
[
1
∼
i
]
s[1 \sim i]
s[1∼i] 的最短循环节,其他循环节长度为 它的整倍数。
求next数组:
// p[0 ~ n - 1]
for (int i = 2, j = 0; i <= n; i++) {
while (j != 0 && p[i - 1] != p[j]) j = ne[j];
if (p[i - 1] == p[j]) j++;
ne[i] = j;
}
// p[1 ~ n]
for (int i = 2, j = 0; i <= n; i++) {
while (j != 0 && p[i] != p[j + 1]) j = ne[j];
if (p[i] == p[j + 1]) j++;
ne[i] = j;
}
代码
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StreamTokenizer;
/**
* @Description
* @Author: PrinceHan
* @CreateTime: 2023/3/8 17:26
*/
public class Main {
static final int N = 1000010;
static int[] ne = new int[N];
static int n;
static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
static StreamTokenizer in = new StreamTokenizer(br);
static PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
public static int nextInt() throws IOException {
in.nextToken();
return (int) in.nval;
}
public static void main(String[] args) throws IOException {
int idx = 1;
while (true) {
n = Integer.parseInt(br.readLine());
if (n == 0) break;
String s = br.readLine();
char[] p = s.toCharArray();
for (int i = 2, j = 0; i <= n; i++) {
while (j != 0 && p[i - 1] != p[j]) j = ne[j];
if (p[i - 1] == p[j]) j++;
ne[i] = j;
}
out.printf("Test case #%d\n", idx++);
for (int i = 2; i <= n; i++) {
if (ne[i] != 0 && i % (i - ne[i]) == 0)
out.printf("%d %d\n", i, i / (i - ne[i]));
}
out.println();
}
out.flush();
}
}
AcWing 831. 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];
// 匹配成功后的逻辑
}
}
代码:
#include<iostream>
using namespace std;
const int N = 1000010;
char s[N], p[N];
int n, m, ne[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);
}
return 0;
}