♥
KMP
♥
时间复杂度 O(n)
在已知字符串中 寻找 子串
//♥
#include <iostream>
using namespace std;
const int N = 1e5 + 5;
const int M = 1e6 + 5;
int n, m;
char p[N], s[M];
int ne[N];
int main()
{
scanf("%d%s%d%s", &n, p+1, &m, s+1); // 让字符串从 1 开始存
//计算 next
for(int i = 2, j = 0; i <= n; i ++ )
{
while(j && p[j + 1] != p[i]) j = ne[j];
if(p[j + 1] == p[i]) j ++ ;
ne[i] = j;
}
for(int i = 1, j = 0; i <= m; i ++ )
{
while(j && p[j + 1] != s[i]) j = ne[j];
if(p[j + 1] == s[i]) j ++ ;
if(j == n)
{
printf("%d ", i - n);
j = ne[j]; // 好像也可以不用写这句
}
}
return 0;
}
♥
Trie
♥
典型应用:高效的 统计,排序和保存 大量 字符串集合(不仅限于字符串)
//♥
#include <iostream>
using namespace std;
const int N = 1e5 + 5;
int n, cnt = 0;
char op;
string s;
int son[N][26], ct[N]; // son 用于存 孩子结点的下标, ct 用于存数量
void Insert(string s)
{
int p = 0;
for(int i = 0; i < s.size(); i ++ )
{
int u = s[i] - 'a';
if(!son[p][u]) son[p][u] = ++ cnt; // 有 孩子 结点后,再启用内存
// son 数组用于指引 孩子结点的 “p” 为多少
p = son[p][u];
}
ct[p] ++ ;
}
int Query(string s)
{
int p = 0;
for(int i = 0; i < s.size(); i ++ )
{
int u = s[i] - 'a';
if(!son[p][u]) return 0;
p = son[p][u];
}
return ct[p];
}
int main()
{
cin >> n;
while(n --)
{
cin >> op >> s;
if(op == 'I') Insert(s);
else cout << Query(s) << endl;
}
return 0;
}
//♥
#include <iostream>
using namespace std;
const int N = 1e5 + 5;
const int M = 31e5 + 5;
int n, cnt;
int son[M][2], a[N];
void Insert(int x)
{
/*
Q: 为什么 i 一定要从 30 到 0 ,不能反过来?
A: 流程是 寻找这一位是否存在 “反数”,
若存在则确定该位的值,继续寻找下一位
若从 0 到 30,则是优先保证低位存在“反数”,可凑 1
不一定能保证高位
而从 30 到 0,是优先保证高位为 1
先要保证高位尽可能大,才能找到最大值
例如:
要找 6(110)与 7(111)和 0(000),哪一队组合异或值更大
若先确保低位,0 要找 1,会选中 7 (111) 分支,最后结果为 1
若先确保高位,1 要找 0,会选中 0 (000) 分支,最后结果为 6(110)
*/
int p = 0;
for(int i = 30; i >= 0; i -- ) // i 表示右移的位数
{
int u = x >> i & 1; // 取 右移后 二进制最低位上的数字
if(!son[p][u]) son[p][u] = ++ cnt;
p = son[p][u];
}
}
int Query(int x)
{
int res = 0, p = 0;
for(int i = 30; i >= 0; i -- )
{
int u = x >> i & 1;
if(son[p][!u])
{
res += 1 << i;
p = son[p][!u];
}
else p = son[p][u];
}
return res;
}
int main()
{
cin >> n;
for(int i = 0; i < n; i ++ )
{
cin >> a[i];
Insert(a[i]);
}
int ans = 0;
for(int i = 0; i < n; i ++ ) ans = max(ans, Query(a[i]));
cout << ans << endl;
return 0;
}
♥
并查集
♥
- 将两个集合 合并
- 查询两个元素是否在一个集合中
//♥
#include <iostream>
using namespace std;
int const N = 1e5 + 5;
int n, m;
int p[N], r[N];
int Find(int x)
{
if(p[x] != x)
{
int px = Find(p[x]);
r[p[x]] = r[px]; // 根据题目需要 变通该式
p[x] = px;
}
return p[x]
}
int main()
{
cin >> n >> m;
for(int i = 1; i <= n; i ++ ) p[i] = i;
while(m --)
{
char op;
int x, y;
cin >> op >> x >> y;
int px = Find(x), py = Find(y);
if(op == 'M')
{
if(px != py) p[px] = py;
}
else
{
if(px == py) puts("Yes");
else puts("No");
}
}
return 0;
}
♥ 典中典拓展域并查集
// ♥
#include <iostream>
using namespace std;
const int N = 50010;
int n, k, ans = 0;
int p[3*N]; // [1, n]同类 [n+1, 2n]食物 [2n+1, 3n]天敌
int Find(int x)
{
if(p[x] != x) p[x] = Find(p[x]);
return p[x];
}
void Unite(int x, int y)
{
int px = Find(x), py = Find(y);
if(px != py) p[px] = py;
}
int main()
{
cin >> n >> k;
for(int i = 1; i <= 3*n; i ++ ) p[i] = i;
while(k --)
{
int op, x, y;
scanf("%d%d%d",&op,&x,&y);
if(x > n || y > n)
{
ans ++ ;
continue;
}
if(op == 1)
{
if(Find(x) == Find(y + n) || Find(x) == Find(y + 2*n)) ans ++ ;
else
{
Unite(x, y);
Unite(x + n, y + n);
Unite(x + 2*n, y + 2*n);
}
}
else
{
if(Find(x) == Find(y) || Find(x) == Find(y + n)) ans ++ ;
else
{
Unite(x, y + 2*n);
Unite(x + n, y);
Unite(x + 2*n, y + n);
}
}
}
cout << ans << endl;
return 0;
}
♥ codeforces.D - Mahmoud and a Dictionary
// ♥
#include <iostream>
#include <unordered_map>
using namespace std;
const int N = 1e5 + 5;
int q, n, m, idx = 0;
int p[2*N]; // [1, n]同义 [n+1, 2*n]反义
unordered_map<string, int> mp;
int Find(int x)
{
if(p[x] != x) p[x] = Find(p[x]);
return p[x];
}
void Unite(int x, int y)
{
int px = Find(x), py = Find(y);
if(px != py) p[px] = py;
}
int main()
{
cin >> n >> m >> q;
for(int i = 1; i <= 2*n; i ++ ) p[i] = i;
for(int i = 1; i <= n; i ++ )
{
string s;
cin >> s;
mp[s] = ++ idx;
}
string a, b;
while(m --)
{
int op;
scanf("%d", &op);
cin >> a >> b;
int x = mp[a], y = mp[b];
if(op == 1)
{
if(Find(x) != Find(y + n))
{
Unite(x, y);
Unite(x + n, y + n);
printf("YES\n");
}
else printf("NO\n");
}
else
{
if(Find(x) != Find(y))
{
Unite(x, y + n);
Unite(x + n, y);
printf("YES\n");
}
else printf("NO\n");
}
}
while(q --)
{
cin >> a >> b;
int x = mp[a], y = mp[b];
if(Find(x) == Find(y)) printf("1\n");
else if(Find(x) == Find(y + n)) printf("2\n");
else printf("3\n");
}
return 0;
}
♥
顺便试了试 输出 哪个快 (好像不明显)
♥
堆 (Heap)
♥
//♥
#include <iostream>
using namespace std;
const int N = 1e5 + 5;
int n, m;
int h[N], cnt = 0;
void down(int i)
{
int t = i;
if(i*2 <= cnt && h[i<<1] < h[t]) t = i << 1;
if(i*2 + 1 <= cnt && h[i<<1|1] < h[t]) t = i << 1 | 1;
if(t != i)
{
swap(h[i], h[t]);
down(t); // ☆向下更新
}
}
int main()
{
cin >> n >> m;
for(int i = 1; i <= n; i ++ ) cin >> h[++cnt];
for(int i = n / 2; i > 0; i --) down(i);
while(m --)
{
cout << h[1] << ' ';
swap(h[1], h[cnt]);
cnt -- ;
down(1);
}
return 0;
}
//♥
#include <iostream>
using namespace std;
const int N = 1e5 + 5;
int n;
int h[N], idx = 0; // idx 有 序号、下标 的意思
int k[N], pk[N], cnt = 0; // k 存储第 k 个数下标,ph 存储 h[i] 的cnt,即 k 数组下标
// 即 k[i] = j; pk[j] = i;
void heap_swap(int i, int j)
{
swap(h[i], h[j]);
swap(k[pk[i]], k[pk[j]]); // 先通过 pk 寻找对应的 k 的下标,交换 k
swap(pk[i], pk[j]); // 再交换 pk 指引对应 k 的下标
}
void up(int i)
{
if((i>>1) > 0 && h[i] < h[i>>1]) // 判断 上方是否存在结点 ,0 默认为空的结点
{
heap_swap(i, i>>1);
up(i>>1); // 继续向上递归
}
}
void down(int i)
{
int t = i;
if((i << 1) <= idx && h[t] > h[i<<1]) t = i << 1;
if((i << 1|1) <= idx && h[t] > h[i<<1|1]) t = i << 1|1;
if(t != i)
{
heap_swap(i, t);
down(t);
}
}
int main()
{
cin >> n;
while(n --)
{
string op;
cin >> op;
if(op == "I")
{
cin >> h[ ++ idx];
k[ ++ cnt] = idx; // 记录 第 cnt 个输入的数 对应 h 数组中的下标
pk[idx] = cnt; // 记录 h[idx] 对应 k 数组中的下标
up(idx);
}
else if(op == "PM") cout << h[1] << endl;
else if(op == "DM")
{
heap_swap(1, idx);
-- idx;
down(1);
}
else if(op == "D")
{
int kk;
cin >> kk;
kk = k[kk]; // 查询 第 kk 个数 在 h 数组中的下标
heap_swap(kk, idx);
-- idx;
up(kk);
down(kk);
}
else
{
int kk, x;
cin >> kk >> x;
kk = k[kk];
h[kk] = x;
up(kk);
down(kk);
}
}
return 0;
}
♥
哈希表
♥
当数据范围很大,而数据数量较小时使用
例如 -109 ≤ ai ≤ 109, 1 ≤ N ≤ 105;
♥ 关于 0x3f3f3f3f 到底有多大(第一个是数字 0)
3f3f3f3f > 109
♥ 两个相加不会超过 int
♥
开放寻址法
//♥
#include <iostream>
#include <cstring>
using namespace std;
#define INF 0x3f3f3f3f
const int N = 2e5 + 3; // 尽量开 2 ~ 3 倍 减少冲突事件,利用空间节省时间
// + 3 ,质数 也是为了减少冲突事件
int n;
int h[N];
int Find(int x)
{
int t = (x % N + N) % N; // (x % N + N) 防止出现负数
while(h[t] != INF && h[t] != x)
{
t ++ ;
if(t == N) t = 0;
}
return t;
}
int main()
{
memset(h, 0x3f, sizeof(h)); // 0x3f3f3f3f > 1e9, 是范围外的数
// 可以当做 判断 h[i] 是否为空的标记
cin >> n;
while(n --)
{
char op;
int x;
cin >> op >> x;
if(op == 'I') h[Find(x)] = x;
else
{
if(h[Find(x)] != INF) puts("Yes");
else puts("No");
}
}
return 0;
}
♥
拉链法
// ♥
#include <iostream>
#include <cstring>
using namespace std;
const int N = 1e5 + 3;
int n, idx = 0;
int e[N], ne[N], h[N]; // e 存储元素,ne 存储 上一个取模结果相等元素 的下标,
// h[i] 存储最新 取模结果为 i 的元素 在 e 中的下标
void Insert(int x)
{
int t = (x % N + N) % N; // (x % N + N) 防止出现负数
e[idx] = x;
ne[idx] = h[t];
h[t] = idx ++ ;
}
int Find(int x)
{
int t = (x % N + N) % N;
for(int i = h[t]; i != -1; i = ne[i])
{
if(e[i] == x) return 1;
}
return 0;
}
int main()
{
cin >> n;
memset(h, -1, sizeof(h));
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;
}
♥ 字符串哈希
看做一个 P 进制的 数值
P 一般取 131 或 13331
//♥
#include <iostream>
using namespace std;
typedef unsigned long long ULL; // 用 unsigned 可不用取模
// 如数据溢出,等价于对 2^64 取模
const int N = 1e5 + 5;
const int P = 131; // 通常取 131 或 13331
int n, m;
char str[N];
ULL h[N], p[N]; // p[i] 表示 p^i 的值
ULL Find(int l, int r)
{
return h[r] - h[l - 1] * p[r - l + 1];
}
int main()
{
cin >> n >> m >> str + 1; // +1 从下标 1 开始存储
p[0] = 1; // 不初始化 p 数组中的值会一直为 0
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(Find(l1, r1) == Find(l2, r2)) puts("Yes");
else puts("No");
}
return 0;
}
♥