# 算法基础
# 常用模版
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #include <bits/stdc++.h> #include <algorithm> > #include <iostream> #include <cstring> #include <vector> #include <cmath> #include <queue> #include <deque> using namespace std;#define int long long void solve () {} signed main () { ios::sync_with_stdio (0 ); cin.tie (0 ); cout.tie (0 ); int t; cin >> t; while (t--) { solve (); } return 0 ; }
# 读入优化
1 2 std::ios::sync_with_stdio (0 ); std::cin.tie (0 );
# 读入方式
当未知要读入几组数据时可以用 while(scanf("%d%d", &n, &m) != -1)
# 离散化
1 2 3 sort (alls.begin (), alls.end ());alls.erase (unique (alls.begin (), alls.end ()), alls.end ());
# double 的 inf
1 2 #include <limits> double inf = std::numeric_limits<double >::infinity ();
# STL
# lower_bound
(左边界取等)
在从小到大排序的数组中:lower_bound (begin, end, num)
从数组的 begin 位置到 end - 1 位置二分查找第一个大于或等于 \color{red}{大于或等于} 大 于 或 等 于 num 的数,找到返回该数的地址,不存在返回 end;通过返回的地址减去 begin,得到数字在数组中的下标
在从大到小排序的数组中:lower_bound (begin ,end, num, greater())
从数组的 begin 位置到 end - 1 位置二分查找第一个小于或等于 \color{red}{小于或等于} 小 于 或 等 于 num 的数
# upper_bound
在从小到大排序的数组中:upper_bound (begin, end, num)
从数组的 begin 位置到 end - 1 位置二分查找第一个大于 \color{red}{大于} 大 于 num 的数,找到返回该数的地址,不存在返回 end;通过返回的地址减去 begin,得到数字在数组中的下标
在从大到小排序的数组中:upper_bound (begin ,end, num, greater())
从数组的 begin 位置到 end - 1 位置二分查找第一个小于 \color{red}{小于} 小 于 num 的数
# vector
操作:
push_back()
pop_back()
size()
clear()
insert()
erase()
erase (_position) -- 删除某个元素
erase (position_start, position_end)–删除区间[ p o s i t i o n _ s t a r t , p o s i t i o n _ e n d ) [position\_start,position\_end) [ p o s i t i o n _ s t a r t , p o s i t i o n _ e n d ) 内的元素
# set
特点:
set 的含义是集合,所有操作都在O ( l o g n ) O(logn) O ( l o g n ) 的时间复杂度内完成
set 插入的元素不能相同
所有元素会根据值排序 (默认是从小到大), set<int, greater<int>>
就是从大到小
set 中元素的值不能被直接修改
set 不支持下标访问操作,只支持迭代器
模版:set s;
操作:
begin ()–返回指向第一个元素的迭代器
end ()–返回指向最后一个元素的迭代器
clear()
empty () -- 如果集合为空,返回 true,否则返回 false
erase()
count()
find(k)
insert()/emplace()
lower_bound (k) – 返回一个迭代器,指向键值大于等于 k 的第一个元素
upper_bound (k) -- 返回一个迭代器,指向键值大于 k 的第一个元素
size()
# queue
操作:
模板:queue <数据类型,容器类型> q
push()
pop()
size()
front ()–返回队首
back ()–返回队尾
empty()
# priority_queue
特点:
包含头文件#include <queue> \verb|#include <queue>| #include <queue>
默认从大到小排序(less 从大到小,greater 从小到大)
操作:
push()
pop()
empty()
top()
size()
# map
特点:
使用头文件#include <map> \verb|#include <map>| #include <map>
具有唯一键值对
模版:map<key_type, value_type> 变量名
可以保证元素的有序性,默认按照键(key)从小到大排序
如果想从大到小排序:map<string, int, greater<string> > m; \verb|map<string, int, greater<string> > m;| map<string, int, greater<string> > m;
操作:
size()
count()
empty()
erase()
clear()
find()
insert()
begin()
end()
lower_bound()
upper_bound()
# unordered_map
特点:
快速查找特定元素
存储时元素是无序的
头文件#include <unordered_map> \verb|#include <unordered_map>| #include <unordered_map>
操作:
插入:insert ({key, value})/map [key] = value
删除:clear ()/erase ()
迭代器:begin ()/end ()
元素个数:size ()/count ()
# pair
1 2 3 typedef pair<int , int > PII;vector<PII> a; int l = a[0 ].first, int r = a[0 ].second;
# multiset
multiset 是< s e t > <set> < s e t > 库中一个非常有用的类型,它可以看成一个序列,插入一个数,删除一个数都能够在O ( l o g n ) O(logn) O ( l o g n ) 的时间内完成,而且他能时刻保证序列中的数是有序的 ,而且序列中可以存在重复 的数。
# 二分查找
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 int dic_left (int k) { int l = 0 , r = n - 1 ; while (l < r) { int mid = l + r >> 1 ; if (a[mid] < k) l = mid + 1 ; else r = mid; } return l; } int dic_right (int k) { int l = 0 , r = n - 1 ; while (l < r) { int mid = l + r + 1 >> 1 ; if (a[mid] > k) r = mid - 1 ; else l = mid; } return l; }
# 前缀和
一维前缀和:
s[i] = s[i - 1] + a[i]
数列 a 中任意 [ l , r ] \text\ [l, r] [ l , r ] 的和为 s[r] - s[l - 1]
二维前缀和:
s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + a[i][j]
求以 ( x 1 , y 1 ) \text\ (x1, y1) ( x 1 , y 1 ) 为左上角, ( x 2 , y 2 ) \text\ (x2, y2) ( x 2 , y 2 ) 为右下角的子矩阵元素和 s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1]
# 差分
差分序列的前缀和序列就是原序列,利用差分数组将 “区间操作” 变成 “单点操作”
一维差分数组:
dp[i] = a[i] - a[i - 1]
把 a 数组的 [l, r] 上的每个数加上 c,则 dp[l] += c, dp[r + 1] -= c;
对差分数组求前缀和 a[i] = dp[i] + a[i - 1]
二维差分数组:
dp[i][j] = a[i][j] - a[i - 1][j] - a[i][j - 1] + a[i - 1][j - 1]
把矩阵中左上角为 a [ x 1 ] [ y 1 ] \text\ a[x1][y1] a [ x 1 ] [ y 1 ] 右下角为 a [ x 2 ] [ y 2 ] \text\ a[x2][y2] a [ x 2 ] [ y 2 ] 的小矩形中每个元素加上 c,则
1 2 3 4 dp[x1][y1] += c; dp[x1][y2 + 1 ] -= c; dp[x2 + 1 ][y1] -= c; dp[x2 + 1 ][y2 + 1 ] += c;
对差分矩阵求前缀和得原矩阵
即 a[i][j] = dp[i][j] + dp[i - 1][j] + dp[i][j - 1] - dp[i - 1][j - 1]
# 位运算
# 二进制状态压缩
& 1 2 3 4 5 6 7 8 9 10 11 12  ### 位运算交换两数 ```c++ void swap(int& a, int &b) { a ^= b; b ^= a; a ^= b; }
# lowbit 运算
lowbit (n) 运算表示非负整数 n 在二进制表示下 “最低位的 1 和其后所有的 0” 所构成的数值 lowbit(n) = n & (~n + 1) = n & (-n)
# 数据结构
# 单调栈
寻找某个数左边第一个小于它的数
维护一个栈,栈顶元素即是当前准备入栈元素的左边第一个小于它的数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <iostream> using namespace std;const int N = 1e5 + 10 ;int st[N], tt = 0 ;int main () { int n; cin >> n; while (n--) { int x; cin >> x; while (tt && st[tt] >= x) tt--; if (!tt) cout << -1 << " " ; else cout << st[tt] << " " ; st[++tt] = x; } return 0 ; }
# 单调队列
滑动窗口:找区间最大值和最小值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 #include <iostream> using namespace std;const int N = 1e6 + 10 ;int q[N], a[N];int main () { std::ios::sync_with_stdio (0 ); std::cin.tie (0 ); int n, k; cin >> n >> k; for (int i = 0 ; i < n; i++) cin >> a[i]; int hh = 0 , tt = -1 ; for (int i = 0 ; i < n; i++) { if (hh <= tt && i - k + 1 > q[hh]) ++hh; while (hh <= tt && a[i] <= a[q[tt]]) --tt; q[++tt] = i; if (i >= k - 1 ) cout << a[q[hh]] << " " ; } cout << '\n' ; hh = 0 , tt = -1 ; for (int i = 0 ; i < n; i++) { if (hh <= tt && i - k + 1 > q[hh]) ++hh; while (hh <= tt && a[i] >= a[q[tt]]) --tt; q[++tt] = i; if (i >= k - 1 ) cout << a[q[hh]] << " " ; } return 0 ; }
# 并查集
1 2 3 4 5 6 for (int i = 1 ; i <= n; i++) p[i] = i;int find (int x) { if (x != p[x]) p[x] = find (p[x]); return p[x]; }
# 树状数组
# 线段树
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 struct Node { int l, r; int v; }tree[N * 4 ]; void build (int u, int l, int r) { tree[u] = {l,r}; if (l == r) return ; int mid = (l + r) >> 1 ; build (u << 1 , l, mid), build (u << 1 | 1 , mid + 1 , r); } void modify (int u, int x, int v) { if (tree[u].l == x && tree[u].r == x) { tree[u].v = v; return ; } int mid = (tree[u].l + tree[u].r) >> 1 ; if (x <= mid) modify (u << 1 , x, v); else modify (u << 1 | 1 , x, v); tree[u].v = max (tree[u << 1 ].v, tree[u << 1 | 1 ].v); } int query (int u, int l, int r) { if (l <= tree[u].l && r >= tree[u].r) return tree[u].v; int mid = (tree[u].l + tree[u].r) >> 1 ; int v = 0 ; if (l <= mid) v = query (u << 1 , l, r); if (r > mid) v = max (v, query (u << 1 | 1 , l, r)); return v; }
带懒标记的线段树
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 struct Node { int l, r; int sum, tag; }tree[N * 4 ]; void pushup (int u) { tree[u].sum = tree[u << 1 ].sum + tree[u << 1 | 1 ].sum; } void pushdown (int u) { Node& root = tree[u], & left = tree[u << 1 ], & right = tree[u << 1 | 1 ]; if (root.tag) { left.tag += root.tag, left.sum += (left.r - left.l + 1 ) * root.tag; right.tag += root.tag, right.sum += (right.r - right.l + 1 ) * root.tag; root.tag = 0 ; } } void build (int u, int l, int r) { if (l == r) { tree[u] = {l, r, a[l], 0 }; return ; } tree[u] = {l, r}; int mid = (l + r) >> 1 ; build (u << 1 , l, mid), build (u << 1 | 1 , mid + 1 , r); pushup (u); } void modify (int u, int l, int r, int v) { if (tree[u].l >= l && tree[u].r <= r) { tree[u].sum += (tree[u].r - tree[u].l + 1 ) * v; tree[u].tag += v; } else { pushdown (u); int mid = (tree[u].l + tree[u].r) >> 1 ; if (l <= mid) modify (u << 1 , l, r, v); if (r > mid) modify (u << 1 | 1 , l, r, v); pushup (u); } } int query (int u, int l, int r) { if (tree[u]. l >= l && tree[u].r <= r) { return tree[u].sum; } pushdown (u); int mid = (tree[u].l + tree[u].r) >> 1 ; int s = 0 ; if (l <= mid) s = query (u << 1 , l, r); if (r > mid) s += query (u << 1 | 1 , l, r); return s; }
# 字符串哈希
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 #include <iostream> #include <cstring> using namespace std;#define int unsigned long long const int P = 131 , N = 1e5 + 10 ;int h[N], p[N];int query (int l, int r) { return h[r] - h[l - 1 ] * p[r - l + 1 ]; } signed main () { int n, m; cin >> n >> m; string x; cin >> x; p[0 ] = 1 ; 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)) cout << "Yes" << '\n' ; else cout << "No" << '\n' ; } return 0 ; }
# Trie 字符串统计
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 #include <iostream> #include <cstring> using namespace std;const int N = 1e5 + 10 ;int son[N][26 ], cnt[N], idx;void Insert (string s) { int p = 0 ; for (int i = 0 ;s[i]; i++) { int u = s[i] - 'a' ; if (!son[p][u]) son[p][u] = ++idx; p = son[p][u]; } cnt[p]++; } int Query (string s) { int p = 0 ; for (int i = 0 ; s[i]; i++) { int u = s[i] - 'a' ; if (son[p][u]) p = son[p][u]; else return 0 ; } return cnt[p]; } int main () { int n; cin >> n; while (n--) { char op; cin >> op; string s; cin >> s; if (op == 'I' ) { Insert (s); } else { cout << Query (s) << '\n' ; } } return 0 ; }
# 最大异或对
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 #include <iostream> using namespace std;const int N = 1e5 + 10 , M = 31 * N;int son[M][2 ], idx;int a[N];void Insert (int a) { int p = 0 ; for (int i = 30 ; i >= 0 ; i--) { int u = a >> i & 1 ; if (!son[p][u]) son[p][u] = ++idx; p = son[p][u]; } } int search (int a) { int p = 0 , res = 0 ; for (int i = 30 ; i >= 0 ; i--) { int u = a >> i & 1 ; if (son[p][!u]) { p = son[p][!u]; res = res * 2 + 1 ; } else { p = son[p][u]; res = res * 2 + 0 ; } } return res; } int main () { int n; cin >> n; 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])); cout << res; return 0 ; }
# KMP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #include <iostream> using namespace std;const int N = 1e5 + 10 , M = 1e6 + 10 ;int n, m;char p[N], s[M];int ne[N];int main () { cin >> n >> p + 1 >> m >> s + 1 ; for (int i = 0 , j = 2 ; j <= n; j++) { while (i && p[i + 1 ] != p[j]) i = ne[i]; if (p[i + 1 ] == p[j]) i++; ne[j] = i; } for (int i = 0 , j = 1 ; j <= m; j++) { while (i && p[i + 1 ] != s[j]) i = ne[i]; if (p[i + 1 ] == s[j]) i++; if (i == n) { cout << j - n << " " ; i = ne[i]; } } return 0 ; }
# AC 自动机
KMP:以O ( n ) O(n) O ( n ) 判断一个单词在文章中出现的次数和具体的位置
Trie:计算一个单词出现的次数
AC 自动机:以O ( n ) O(n) O ( n ) 判断多个单词是否在文章中出现
计算一篇文章中出现了多少个列举的单词
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 #include <iostream> #include <cstring> #include <algorithm> using namespace std;const int N = 1e4 + 10 , M = 1e6 + 10 , S = 55 ;int n;int tr[N * S][26 ], cnt[N * S], idx;char str[M];int q[N * S], ne[N * S];void insert () { int p = 0 ; for (int i = 0 ; str[i]; i++) { int t = str[i] - 'a' ; if (!tr[p][t]) tr[p][t] = ++idx; p = tr[p][t]; } cnt[p]++; } void build () { int hh = 0 , tt = -1 ; for (int i = 0 ; i < 26 ; i++) { if (tr[0 ][i]) q[++tt] = tr[0 ][i]; } while (hh <= tt) { int t = q[hh++]; for (int i = 0 ; i < 26 ; i++) { int p = tr[t][i]; if (!p) tr[t][i] = tr[ne[t]][i]; else { ne[p] = tr[ne[t]][i]; q[++tt] = p; } } } } int main () { int T; cin >> T; while (T--) { memset (tr, 0 , sizeof tr); memset (cnt, 0 , sizeof cnt); memset (ne, 0 , sizeof ne); idx = 0 ; cin >> n; for (int i = 0 ; i < n; i++) { cin >> str; insert (); } build (); cin >> str; int res = 0 ; for (int i = 0 , j = 0 ; str[i]; i++) { int t = str[i] - 'a' ; j = tr[j][t]; int p = j; while (p) { res += cnt[p]; cnt[p] = 0 ; p= ne[p]; } } cout << res <<'\n' ; } return 0 ; }
# 图论
# DFS 和 BFS
# DFS
dfs 重点在递归,每次递归需要考虑递归传递的参数、结束条件,并且要及时 “还原” 改变的参数
# 全排列
递归传递的参数:表示当前答案中有多少个数字
递归结束的条件:当前答案中的数字达到 n
递归的过程:设置 st 数组,判断该数字是否被使用过;每次递归后需还原 st 的状态
全排列的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 void dfs (int x) { if (x > n) { for (int i = 1 ; i <= n; i++) cout << res[i] << " " ; cout << '\n' ; return ; } for (int i = 1 ; i <= n; i++) { if (!st[i]) { res[x] = i; st[i] = true ; dfs (x + 1 ); st[i] = false ; } } }
# n 皇后
递归传递的参数:表示当前放到了第几行
递归结束的条件:遍历到了最后一行
递归的过程:每一行利用 for 循环遍历每一列,每次递归后 “还原” place 以及行列斜的状态数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 #include <iostream> using namespace std;const int N = 20 ;char place[N][N];bool row[N], col[N], dg[N], udg[N];int n;void dfs (int x) { if (x == n) { for (int i = 0 ; i < n; i++) { for (int j = 0 ; j < n; j++) { cout << place[i][j]; } cout << '\n' ; } cout << '\n' ; return ; } for (int y = 0 ; y < n; y++) { if (!row[x] && !col[y] && !dg[x + y] && !udg[y - x + n]) { row[x] = col[y] = dg[x + y] = udg[y - x + n] = true ; place[x][y] = 'Q' ; dfs (x + 1 ); row[x] = col[y] = dg[x + y] = udg[y - x + n] = false ; place[x][y] = '.' ; } } } int main () { cin >> n; for (int i = 0 ; i < n; i++) for (int j = 0 ; j < n; j++) place[i][j] = '.' ; dfs (0 ); return 0 ; }
# BFS
BFS 即每次往” 外 “走一圈,利用队列模拟,每次出队时让与之相连的结点加入队列;单独设置一个 “数组”,用来更新距离,并充当标记数组
# 走迷宫
设置方向数组 dx,dy,方便 “行走”
注意 d 数组,不仅充当表示距离的作用,也用来标记该点是否加入过队列中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 int bfs () { memset (d, -1 , sizeof d); q.push ({0 , 0 }); d[0 ][0 ] = 0 ; while (q.size ()) { PII t = q.front (); q.pop (); int a = t.first, b = t.second; for (int i = 0 ; i < 4 ; i++) { int x = a + dx[i], y = b + dy[i]; if (x >= 0 && x < n && y >= 0 && y < m && road[x][y] == 0 && d[x][y] == -1 ) { d[x][y] = d[a][b] + 1 ; q.push ({x, y}); } } } return d[n - 1 ][m - 1 ]; }
# 最短路问题
# 图的宽度优先搜索
与上题的 bfs 类似,只是存图的方式变成了链式前向星
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 #include <iostream> #include <cstring> #include <queue> using namespace std;const int N = 1e5 + 10 ;int head[N], to[N], ne[N], idx;int n, m;int d[N];queue<int > q; void add (int x, int y) { to[idx] = y, ne[idx] = head[x], head[x] = idx++; } int bfs () { q.push (1 ); while (q.size ()) { int t = q.front (); q.pop (); for (int i = head[t]; i != -1 ; i = ne[i]) { int j = to[i]; if (d[j] == -1 ) { d[j] = d[t] + 1 ; q.push (j); } } } return d[n]; } int main () { memset (head, -1 , sizeof head); memset (d, -1 , sizeof d); cin >> n >> m; while (m--) { int a, b; cin >> a >> b; add (a, b); } d[1 ] = 0 ;c++ cout << bfs (); return 0 ; }
# Dijkstra 算法
(不能存在负权边) \color{blue}(不能存在负权边) ( 不 能 存 在 负 权 边 )
朴素版本 :时间复杂度 O ( n 2 ) O(n^2) O ( n 2 ) ,适合稠密图(利用邻接矩阵存储)
将起点距离初始化为 0,其他点的初始距离设置为无穷
每次找到距离起点距离最近的节点,将该点做标记,表示已经设置过
更新与该点有关的结点的距离
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 #include <iostream> #include <cstring> using namespace std;const int N = 510 ;const int M = 1e5 + 10 ;int n, m;int g[N][N];bool st[N];int dist[N];int dijkstra () { dist[1 ] = 0 ; for (int i = 1 ; i <= n - 1 ; i++) { int t = -1 ; for (int j = 1 ; j <= n; j++) { if (!st[j] && (t == -1 || dist[j] < dist[t])) t = j; } st[t] = true ; for (int j = 1 ; j <= n; j++) { if (dist[j] > dist[t] + g[t][j]) dist[j] = dist[t] + g[t][j]; } } if (dist[n] == 0x3f3f3f3f ) return -1 ; return dist[n]; } int main () { cin >> n >> m; memset (g, 0x3f3f , sizeof (g)); memset (dist, 0x3f3f , sizeof dist); while (m--) { int x, y, z; cin >> x >> y >> z; g[x][y] = min (g[x][y], z); } cout << dijkstra (); }
利用堆排序优化:时间复杂度 **O ( m l o g n ) O(mlogn) O ( m l o g n ) **,适合稀疏图(利用邻接表存储)( 除了找最小距离的时候利用堆优化,其他与朴素算法一致 ) {\color{red}(除了找最小距离的时候利用堆优化,其他与朴素算法一致)} ( 除 了 找 最 小 距 离 的 时 候 利 用 堆 优 化 , 其 他 与 朴 素 算 法 一 致 )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 #include <iostream> #include <cstring> #include <queue> using namespace std;const int N = 1e5 + 5e4 + 10 ;int head[N], e[N], ne[N], w[N],idx;int dist[N];bool st[N];int n, m;typedef pair<int , int > PII;priority_queue<PII,vector<PII>, greater<PII>> heap; void add (int x, int y, int z) { w[idx] = z; e[idx] = y; ne[idx] = head[x]; head[x] = idx++; } int dijsktra () { memset (dist, 0x3f3f , sizeof dist); dist[1 ] = 0 ; heap.push ({0 , 1 }); while (heap.size ()) { PII q = heap.top (); int a = q.second, b = q.first; heap.pop (); if (st[a]) continue ; st[a] = true ; for (int i = head[a]; i != -1 ; i = ne[i]) { int j = e[i]; if (dist[j] > dist[a] + w[i]) { dist[j] = dist[a] + w[i]; heap.push ({dist[j], j}); } } } if (dist[n] == 0x3f3f3f3f ) return -1 ; return dist[n]; } int main () { memset (head, -1 , sizeof head); cin >> n >> m; while (m--) { int x, y, z; cin >> x >> y >> z; add (x, y, z); } cout << dijsktra (); return 0 ; }
# BellmanFord 算法
求出从 1 号点到 n 号点的最多经过 k 条边的最短距离
可以处理负边权回路
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 #include <iostream> #include <cstring> using namespace std;const int N = 510 , M = 10010 ;int n, m, k;int dist[N], cpy[N];struct Edge { int a, b, w; }edges[M]; void BellmanFord () { memset (dist, 0x3f3f , sizeof dist); dist[1 ] = 0 ; for (int i = 0 ; i < k; i++) { memcpy (cpy, dist, sizeof dist); for (int j = 0 ; j < m; j++) { auto e = edges[j]; dist[e.b] = min (dist[e.b], cpy[e.a] + e.w); } } } int main () { cin >> n >> m >> k; int x, y, z; for (int i = 0 ; i < m; i++) { cin >> x >> y >> z; edges[i].a = x, edges[i].b = y, edges[i].w = z; } BellmanFord (); if (dist[n] > 0x3f3f3f3f / 2 ) puts ("impossible" ); else cout << dist[n]; return 0 ; }
# Spfa 算法
类似于宽搜
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 void spfa () { memset (dist, 0x3f , sizeof dist); dist[1 ] = 0 ; queue<int > q; q.push (1 ); st[1 ] = true ; while (q.size ()) { int t = q.front (); q.pop (); st[t] = false ; for (int i = head[t]; i != -1 ; i = ne[i]) { int j = e[i]; if (dist[j] > dist[t] + w[i]) { dist[j] = dist[t] + w[i]; if (!st[j]) { q.push (j); st[j] = true ; } } } } }
# spfa 判断负环
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 #include <iostream> #include <queue> #include <cstring> using namespace std;const int N = 2010 ,M = 10010 ;int to[M], ne[M], w[M], head[N], idx;bool st[N];int dist[N], cnt[N];int n, m;void add (int x, int y, int z) { to[idx] = y, ne[idx] = head[x], w[idx] = z, head[x] = idx++; } bool spfa () { memset (dist, 0x3f , sizeof dist); dist[1 ] = 0 ; queue<int > q; for (int i = 1 ; i <= n; i++) { q.push (i); st[i] = true ; } while (q.size ()) { int t = q.front (); q.pop (); st[t] = false ; for (int i = head[t]; i != -1 ; i = ne[i]) { int j = to[i]; if (dist[j] > dist[t] + w[i]) { dist[j] = dist[t] + w[i]; cnt[j] = cnt[t] + 1 ; if (cnt[j] >= n) return true ; if (!st[j]) { q.push (j); st[j] = true ; } } } } return false ; } int main () { memset (head, -1 , sizeof head); cin >> n >> m; while (m--) { int x, y, z; cin >> x >> y >> z; add (x, y, z); } if (spfa ()) cout << "Yes" ; else cout << "No" ; return 0 ; }
# Floyd 算法(多源最短路)
将所有情况一一列出来
1 2 3 4 5 6 7 void floyd () { for (int k = 1 ; k <= n; k++) for (int i = 1 ; i <= n; i++) for (int j = 1 ; j <= n; j++) d[i][j] = min (d[i][j], d[i][k] + d[k][j]); }
# 拓扑排序
每次找到度为 0 的节点加入到最终的序列中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 #include <iostream> #include <cstring> using namespace std;const int N = 1e5 + 10 ;int head[N], e[N], ne[N], idx;int d[N];int q[N];int n, m;void add (int x, int y) { e[idx] = y; ne[idx] = head[x]; head[x] = idx++; } bool TopSort () { int hh = 0 , tt = 0 ; for (int i = 1 ; i <= n; i++) if (!d[i]) q[tt++] = i; while (hh <= tt) { int t = q[hh++]; for (int i = head[t]; i != -1 ; i = ne[i]) { int j = e[i]; if (--d[j] == 0 ) q[tt++] = j; } } return tt == n; } int main () { memset (head, -1 , sizeof (head)); cin >> n >> m; while (m--) { int x, y; cin >> x >> y; d[y]++; add (x, y); } if (TopSort ()) { for (int i = 0 ; i < n; i++) cout << q[i] << " " ; } else cout << -1 ; return 0 ; }
# 数论
# 质数
# 筛法求质数
1 2 3 4 5 6 7 8 9 10 11 12 13 int get_prime (int n) { for (int i = 2 ; i <= n; i++) { if (!st[i]) prime[cnt++] = i; for (int j = 0 ; prime[j] * i * 1ll <= n; j++) { st[i * prime[j]] = true ; if (i % prime[j] == 0 ) break ; } } return cnt; }
# 二次筛质数
用二次筛:假设一个数 n 是合数,那么它一定有d , n d d,\frac{n}{d} d , d n 两个因子,假设d < n d d<\frac{n}{d} d < d n ,则d < n d<\sqrt{n} d < n ,所以只需要找出1 ∼ n 1\sim\sqrt{n} 1 ∼ n 的所有素数,然后用这些素数更新[ l , r ] [l,r] [ l , r ] 的所有合数即可
对于素数 p,要找到[ l , r ] [l,r] [ l , r ] 中所有以 p 为最小质因子的合数,可以从 max(2 * p, (l + p - 1) / p * p)
开始寻找,然后不断 +p
即可,其中 (l + p - 1) / p * p
等价于⌈ l p ⌉ \lceil\frac{l}{p}\rceil ⌈ p l ⌉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 #include <bits/stdc++.h> using namespace std;const int N = 1e6 + 10 ;typedef long long ll;int prime[N], cnt;ll prime2[N], k; bool st[N];void get_prime (int n) { for (int i = 2 ; i <= n; i++) { if (!st[i]) prime[cnt++] = i; for (int j = 0 ; prime[j] <= n / i; j++) { st[prime[j] * i] = true ; if (i % prime[j] == 0 ) break ; } } } int main () { int l, r; get_prime (50000 ); while (scanf ("%d%d" , &l, &r) != -1 ) { memset (st, 0 , sizeof st); memset (prime2, 0 , sizeof prime2); k = 0 ; for (ll i = 0 ; i < cnt; i++) { ll p = prime[i]; for (ll j = max (2 * p, (l + p - 1 ) / p * p); j <= r; j += p) st[j - l] = true ; } for (int i = 0 ; i <= r - l; i++) { if (!st[i] && i + l > 1 ) prime2[k++] = i + l; } if (k < 2 ) cout << "There are no adjacent primes." << '\n' ; else { int minp = 0 , maxp = 0 ; for (int i = 0 ; i + 1 < k; i++) { int d = prime2[i + 1 ] - prime2[i]; if (d < prime2[minp + 1 ] - prime2[minp]) minp = i; else if (d > prime2[maxp + 1 ] - prime2[maxp]) maxp = i; } printf ("%d,%d are closest, %d,%d are most distant.\n" , prime2[minp], prime2[minp + 1 ], prime2[maxp], prime2[maxp + 1 ]); } } return 0 ; }
# 约数
# 约数个数
把一个数 N 写成N = p 1 x 1 ⋅ p 2 x 2 ⋅ . . . ⋅ p k x k N=p_1 ^ {x_1}\cdot p_2^{x_2}\cdot ... \cdot p_k^{x_k} N = p 1 x 1 ⋅ p 2 x 2 ⋅ . . . ⋅ p k x k , 则约数个数为( x 1 + 1 ) ( x 2 + 1 ) . . . ( x k + 1 ) (x_1+1)(x_2+1)...(x_k+1) ( x 1 + 1 ) ( x 2 + 1 ) . . . ( x k + 1 )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 #include <iostream> #include <unordered_map> using namespace std;const int mod = 1e9 +7 ;int main () { int n; cin >>n; unordered_map<int , int > mp; while (n--) { int a; cin >>a; for (int i = 2 ; i <= a / i; i++) { while (a % i == 0 ) { mp[i]++; a /= i; } } if (a >1 ) mp[a]++; } long long ret = 1 ; for (auto & [x, y] : mp) { ret = ret * (y + 1 ) % mod; } cout << ret % mod; return 0 ; }
# 约数之和
把一个数 N 写成N = p 1 x 1 ⋅ p 2 x 2 ⋅ . . . ⋅ p k x k N=p_1 ^ {x_1}\cdot p_2^{x_2}\cdot ... \cdot p_k^{x_k} N = p 1 x 1 ⋅ p 2 x 2 ⋅ . . . ⋅ p k x k , 则约数之和为( p 1 0 + p 1 1 + . . . + p 1 x 1 ) × . . . × ( p k 0 + p k 1 + . . . + p k x k ) ( p_1^0+p_1^1+..._+p_1^{x_1})\times...\times (p_k^0+p_k^1+...+p_k^{x_k}) ( p 1 0 + p 1 1 + . . . + p 1 x 1 ) × . . . × ( p k 0 + p k 1 + . . . + p k x k )
1 2 3 4 5 6 7 8 9 10 11 unordered_map<int , int > mp; ll res = 1 ; for (auto & [x, y]: mp){ ll t = 1 ; while (y--) { t = (t * x + 1 ) % mod; } res = res * t % mod; }
# 最大公约数
1 2 3 4 int gcd (int a, int b) { return b == 0 ? a : gcd (b, a % b); }
# 欧拉函数
1∼N 中与 N 互质的数的个数被称为欧拉函数,记为 ϕ(N)。
若在算数基本定理中,N=p_1^{a_1}p_2^{a_2}…p_m^{a_m},则:ϕ(N) = N×\frac{p_1−1}{p1}×\frac{p_2−1}{p2}×…×\frac{p_m−1}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include <iostream> using namespace std;typedef long long ll;int main () { int n; cin >> n; while (n--) { int a; cin >> a; ll res = a; for (int i = 2 ; i <= a / i; i++) { if (a % i == 0 ) res = res / i * (i - 1 ); while (a % i == 0 ) a /= i; } if (a > 1 ) res = res / a * (a - 1 ); cout << res << '\n' ; } return 0 ; }
# 筛法求欧拉函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #include <iostream> using namespace std;const int N = 1e6 + 10 ;typedef long long ll;ll phi[N], prime[N]; bool st[N];ll n, cnt; int main () { cin >> n; phi[1 ] = 1 ; for (int i = 2 ; i <= n; i++) { if (!st[i]) { phi[i] = i - 1 ; prime[cnt++] = i; } for (int j = 0 ; prime[j] <= n / i; j++) { st[prime[j] * i] = true ; if (i % prime[j] == 0 ) { phi[i * prime[j]] = phi[i] * prime[j]; break ; } else phi[i * prime[j]] = phi[i] * (prime[j] - 1 ); } } ll res = 0 ; for (int i = 1 ; i <= n; i++) res += phi[i]; cout << res; return 0 ; }
# 阶乘分解
求阶乘中各个因子的个数
8!中因数 2 的个数: 8 / 2 + 8 / 4 + 8 / 8 = 7
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <iostream> using namespace std;int prime (int x) { if (x == 2 ) return 1 ; for (int i = 2 ; i <= x / i; i++) { if (x % i == 0 ) return 0 ; } return 1 ; } int main () { int n; cin >> n; for (int i = 2 ; i <= n; i++) { if (!prime (i)) continue ; long long x = i, ans = 0 ; while (x <= n) ans += n / x, x *= i; cout << i << " " << ans << '\n' ; } return 0 ; }
# 快速幂
给定 n 组a i , b i , p i a_i,b_i,p_i a i , b i , p i ,对于某组数据,求出a i b i m o d p i a_i^{b_i}modp_i a i b i m o d p i
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #include <iostream> using namespace std;const int N = 1e5 + 10 ;int n;typedef long long ll;ll qmi (ll a, ll b, ll p) { ll ans = 1 ; while (b) { if (b & 1 ) ans = ans * a % p; a = a * a % p; b >>= 1 ; } return ans; } int main () { cin >> n; while (n--) { ll a, b, p; cin >> a >> b >> p; cout << qmi (a, b, p) % p << '\n' ; } return 0 ; }
# 快速幂求逆元
b m − 2 b^{m-2} b m − 2 即为 b 的乘法逆元(m 为 mod 的数)
# 求组合数
# 利用逆元 O (nlogn)
b!^{-1} = ((b-1)!b)^{-1}=(b-1)!^{-1}b^-1 = (b-1)!^{-1}b^
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 #include <iostream> using namespace std;typedef long long ll;const int N = 1e5 + 10 , mod = 1e9 + 7 ;ll f[N], inf[N], n; ll qmi (ll a, ll b, ll p) { ll ans = 1 ; while (b) { if (b & 1 ) ans = ans * a % p; a = a * a % p; b >>= 1 ; } return ans; } void Init () { f[0 ] = inf[0 ] = 1 ; for (int i = 1 ; i < 1e5 + 10 ; i++) { f[i] = f[i - 1 ] * i % mod; inf[i] = inf[i - 1 ] * qmi (i, mod - 2 , mod) % mod; } } int main () { cin >> n; Init (); while (n--) { int a, b; cin >> a >> b; cout << f[a] * inf[b] % mod * inf[a - b] % mod << '\n' ; } return 0 ; }
# lucas 定理
lucas 定理:C a b = C a p b p C a m o d p b m o d p ( m o d p ) C_a^b = C_{\frac{a}{p}}^{\frac{b}{p}}C_{a\ mod\ p}^{b\ mod\ p}(mod\ p) C a b = C p a p b C a m o d p b m o d p ( m o d p )
适用于 p 较小的情况
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 #include <bits/stdc++.h> using namespace std;typedef long long ll;ll a, b, p, n; ll qmi (ll a, ll b) { ll res = 1 ; while (b) { if (b & 1 ) res = res * a % p; a = a * a % p; b >>= 1 ; } return res; } ll C (ll a, ll b) { if (b > a) return 0 ; ll res = 1 ; for (int i = 1 , j = a; i <= b; i++, j--) { res = res * j % p; res = res * qmi (i, p - 2 ) % p; } return res; } ll lucas (ll a, ll b) { if (a < p && b <p) return C (a, b); return C (a % p, b % p) * lucas (a / p, b / p) % p; } int main () { cin >> n; while (n--) { cin >> a >> b >> p; cout << lucas (a, b) << '\n' ; } }
# 高精度
将C a b = a ! b ! ( a − b ) ! C_a^b=\frac{a!}{b!(a-b)!} C a b = b ! ( a − b ) ! a ! 中的a ! b ! ( a − b ) ! − 1 a!\ b!\ (a-b)!^{-1} a ! b ! ( a − b ) ! − 1 用质因子进行表示,只进行上标的减法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 #include <bits/stdc++.h> using namespace std;const int N = 5100 ;int prime[N], st[N], cnt, num[N];int a, b;void get_prime (int x) { for (int i = 2 ; i <= x; i++) { if (!st[i]) prime[cnt++] = i;; for (int j = 0 ; prime[j] <= x / i; j++) { st[i * prime[j]] = true ; if (i % prime[j] == 0 ) break ; } } } int count (int p, int n) { int res = 0 ; while (n) { res += n / p; n /= p; } return res; } vector<int > mul (const vector<int >& A, const int p) { if (p == 0 ) return {0 }; int t = 0 ; vector<int > res; for (int i = 0 ; i < A.size () || t > 0 ; i++) { if (i < A.size ()) t += A[i] * p; res.push_back (t % 10 ); t /= 10 ; } while (res.size () > 0 && res.back () == 0 ) res.pop_back (); return res; } int main () { cin >> a >> b; get_prime (a); for (int i = 0 ; i < cnt; i++) num[i] = count (prime[i], a) - count (prime[i], b) - count (prime[i], a - b); vector<int > res = {1 }; for (int i = 0 ; i < cnt; i++) for (int j = 0 ; j < num[i]; j++) res = mul (res, prime[i]); for (int i = res.size () - 1 ; i >= 0 ; i--) cout << res[i]; return 0 ; }
# 动态规划
# 背包问题
# 01 背包问题
1 2 3 4 5 6 7 8 9 10 int main () { cin >> N >> V; for (int i = 1 ; i <= N; i++) cin >> v[i] >> w[i]; for (int i = 1 ; i <= N; i++) for (int j = V; j >= v[i]; j--) f[j] = max (f[j], f[j - v[i]] + w[i]); cout << f[V]; return 0 ; }
# 完全背包
每个物品可以使用无数次,求拿容量不超过 V 的物品能得到的最大价值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #include <bits/stdc++.h> using namespace std;const int N = 1e3 + 10 ;int v[N], w[N];int f[N];int n, m;int main () { cin >> n >> m; for (int i = 1 ; i <= n; i++) cin >> v[i] >> w[i]; for (int i = 1 ; i <= n; i++) for (int j = v[i]; j <= m; j++) f[j] = max (f[j], f[j - v[i]] + w[i]); cout << f[m]; return 0 ; }
# 多重背包
每个物品只能拿一次,每个物品最多有 s 件,求拿总容量不超过 V 的物品能得到的最大价值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 #include <bits/stdc++.h> using namespace std;const int N = 12010 ;int v[N], w[N];int n, m;int f[N];int main () { cin >> n >> m; int cnt = 0 ; for (int i = 1 ; i <= n; i++) { int a, b, s; cin >> a >> b >> s; int k = 1 ; while (k <= s) { cnt++; v[cnt] = a * k, w[cnt] = b * k; s -= k, k *= 2 ; } if (s > 0 ) { cnt++; v[cnt] = a * s, w[cnt] = b * s; } } n = cnt; for (int i = 1 ; i <= n; i++) for (int j = m; j >= v[i]; j--) f[j] = max (f[j], f[j - v[i]] + w[i]); cout << f[m]; return 0 ; }
# 分组背包
每组有若干个物品,每组只能选一个物品,求拿总容量不超过 V 的物品能得到的最大价值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include <bits/stdc++.h> using namespace std;const int N = 110 ;int f[N];int v[N][N], w[N][N], s[N];int n, m;int main () { cin >> n >> m; for (int i = 1 ; i <= n; i++) { cin >> s[i]; for (int j = 1 ; j <= s[i]; j++) cin >> v[i][j] >> w[i][j]; } for (int i = 1 ; i <= n; i++) for (int j = m; j >= 0 ; j--) for (int k = 0 ; k <= s[i]; k++) if (v[i][k] <= j) f[j] = max (f[j], f[j - v[i][k]] + w[i][k]); cout << f[m]; return 0 ; }
# 线性 Dp
# 最长上升子序列
法一:时间复杂度O ( n 2 ) O(n^2) O ( n 2 )
f ( i ) = m a x ( f ( j ) + 1 ) f(i) = max(f(j)+1) f ( i ) = m a x ( f ( j ) + 1 ) ,其中w [ j ] < w [ i ] , j < i w[j]<w[i],j<i w [ j ] < w [ i ] , j < i
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <iostream> using namespace std;const int N = 1010 ;int w[N], f[N];int n, ans;int main () { cin >> n; for (int i = 1 ; i <= n; i++) cin >> w[i]; for (int i = 1 ; i <= n; i++) { f[i] = 1 ; for (int j = 1 ; j <= i; j++) if (w[i] > w[j]) f[i] = max (f[i], f[j] + 1 ); ans = max (ans, f[i]); } cout << ans; return 0 ; }
方式二:时间复杂度O ( n l o g n ) O(nlogn) O ( n l o g n )
状态表示f ( i ) f(i) f ( i )
集合:表示长度为 i 的序列的最小结尾
属性:序列中最后一个值的最小值
状态计算:
如果w [ i ] w[i] w [ i ] 大于f ( c n t ) f(cnt) f ( c n t ) ,直接加到 cnt 之后即可
如果小于等于f ( c n t ) f(cnt) f ( c n t ) ,找到 f 数组中第一个大于w [ i ] w[i] w [ i ] 的位置,进行替换
答案:f 数组的长度
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include <bits/stdc++.h> using namespace std;const int N = 1e5 + 10 ;int w[N], f[N];int n, cnt;int main () { cin >> n; for (int i = 1 ; i <= n; i++) cin >> w[i]; f[++cnt] = w[1 ]; for (int i = 2 ; i <= n; i++) { if (w[i] > f[cnt]) f[++cnt] = w[i]; else f[lower_bound (f + 1 , f + cnt + 1 , w[i]) - f] = w[i]; } cout << cnt; return 0 ; }
# 最长公共子序列
状态计算:
a [ i ] = = b [ j ] a[i] == b[j] a [ i ] = = b [ j ] ,a [ i ] , b [ j ] a[i],b[j] a [ i ] , b [ j ] 都在,那么最长子序列的长度是前一个 + 1,即f [ i − 1 ] [ j − 1 ] + 1 f[i-1][j-1]+1 f [ i − 1 ] [ j − 1 ] + 1
a [ i ] ! = b [ j ] a[i]!=b[j] a [ i ] ! = b [ j ]
a [ i ] a[i] a [ i ] 在,b [ j ] b[j] b [ j ] 不在:那就是算 A 的前 i 个字符,B 的前 i - 1 个字符的公共子序列的最长长度,即f [ i ] [ j − 1 ] f[i][j - 1] f [ i ] [ j − 1 ]
a [ i ] a[i] a [ i ] 不在,b [ j ] b[j] b [ j ] 在:那就是算 A 的前 i - 1 个字符,B 的前 i 个字符的公共子序列的最长长度,即f [ i − 1 ] [ j ] f[i-1][j] f [ i − 1 ] [ j ]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <bits/stdc++.h> using namespace std;const int N = 1e3 + 10 ;char a[N], b[N];int n, m;int f[N][N];int main () { cin >> n >> m >> a + 1 >> b + 1 ; for (int i = 1 ; i <= n; i++) for (int j = 1 ; j <= m; j++) { f[i][j] = max (f[i - 1 ][j], f[i][j - 1 ]); if (a[i] == b[j]) f[i][j] = max (f[i][j], f[i - 1 ][j - 1 ] + 1 ); } cout << f[n][m]; return 0 ; }
# 区间 Dp
# 石子合并
状态计算:
涉及区间和问题,使用前缀和
f ( i , j ) = f ( i , k ) + f ( k + 1 , j ) + s [ j ] − s [ i − 1 ] f(i,j) = f(i,k)+f(k+1,j)+s[j]-s[i-1] f ( i , j ) = f ( i , k ) + f ( k + 1 , j ) + s [ j ] − s [ i − 1 ]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include <iostream> using namespace std;const int N = 310 ;int f[N][N], a[N];int n;int main () { cin >> n; for (int i = 1 ; i <= n; i++) cin >> a[i], a[i] += a[i - 1 ]; for (int len = 2 ; len <= n; len++) { for (int i = 1 ; i + len - 1 <= n; i++) { int j = i + len - 1 ; f[i][j] = 0x3f3f3f3f ; for (int k = i; k <= j; k++) { f[i][j] = min (f[i][j], f[i][k] + f[k + 1 ][j] + a[j] - a[i - 1 ]); } } } cout << f[1 ][n]; return 0 ; }
# 贪心
# 区间选点
给定 N 个闭区间 [ a i , b i ] [ai,bi] [ a i , b i ] ,请你在数轴上选择尽量少的点,使得每个区间内至少包含一个选出的点。相当于问最少有多少重叠区间的集合
根据观察我们可以根据下一个区间左端点和上一个区间右端点的比较,判断是否有重叠,为了更好的比较就需要排序,那么就有两种解决方法:按右端点排序 / 按左端点排序
按右端点排序:
因为是按右端点排序的,所以在 ed >= e[i].l
的情况下, ed <= e[i].r
是一定的,所以不需要特殊判断
ed = e[i].r
需要放在判断条件的里面,因为这个公共点不在新扩展的那部分区域;如果放在判断条件外面,就相当于默认那段新展开的区域也是有选择的点的,但是那一段新展开的区域显然不是和前面的区间的公共段,所以那个选择的公共点不在新展开的区域
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include <bits/stdc++.h> using namespace std;const int N = 1e5 + 10 ;struct edges { int l, r; bool operator <(const edges& W)const { return r < W.r; } }e[N]; int n;int main () { cin >> n; for (int i = 0 ; i < n; i++) cin >> e[i].l >> e[i].r; sort (e, e + n); int res = 0 , ed = -2e9 ; for (int i = 0 ; i < n; i++) { if (ed < e[i].l) { res++; ed = e[i].r; } } cout << res; return 0 ; }
按左端点排序:
在 ed >= e[i].l
情况下,需要更新公共点所在区间
在 ed < e[i].l
情况下,证明当前区域和之前的区域没有重叠区间,集合数加 1,更新区间边界
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 #include <bits/stdc++.h> using namespace std;const int N = 1e5 + 10 ;struct edges { int l, r; bool operator <(const edges& W)const { return l < W.l; } }e[N]; int n;int main () { cin >> n; for (int i = 0 ; i < n; i++) cin >> e[i].l >> e[i].r; sort (e, e + n); int res = 1 , ed = e[0 ].r; for (int i = 1 ; i < n; i++) { if (ed >= e[i].l) { ed = min (ed, e[i].r); } else { res++; ed = e[i].r; } } cout << res; return 0 ; }
# 最大不相交区间的数量
给定 N 个闭区间[ a i , b i ] [ai,bi] [ a i , b i ] ,请你在数轴上选择若干区间,使得选中的区间之间互不相交(包括端点)。
按照前一题区间选点的方式,将所有区间分成几个集合,每个集合中的各个区间都有公共区域。
若要选择不相交的区域,不相交的区域一定在不同的集合里,所以总集合数就是最大不相交的区域,即区间选点的数量
和区间选点代码相同
# 区间分组
给定 N 个闭区间 [ a i , b i ] [ai,bi] [ a i , b i ] ,请你将这些区间分成若干组,使得每组内部的区间两两之间(包括端点)没有交集,并使得组数尽可能小。
引入小根堆,对区间右端点进行操作比较,如果当前区间的左端点大于最小的区间右端点,可以直接加入到已有的组;否则,新开一个组。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #include <bits/stdc++.h> using namespace std;const int N = 1e5 + 10 ;struct edges { int l, r; bool operator <(const edges& W)const { return l < W.l; } }e[N]; int n;priority_queue<int , vector<int >, greater<int >> q; int main () { cin >> n; for (int i = 0 ; i < n; i++) cin >> e[i].l >> e[i].r; sort (e, e + n); for (int i = 0 ; i < n; i++) { if (q.empty () || q.top () >= e[i].l) q.push (e[i].r); else { q.pop (); q.push (e[i].r); } } cout << q.size (); return 0 ; }
# 区间覆盖
给定 N 个闭区间 [ a i , b i ] [ai,bi] [ a i , b i ] 以及一个线段区间 [ s , t ] [s,t] [ s , t ] ,请你选择尽量少的区间,将指定线段区间完全覆盖。
输出最少区间数,如果无法完全覆盖则输出 −1。
按照区间左端点进行排序,在包含 start 点的所有区间中找到右端点最大的区间
判断条件
如果左端点小于 start,就将它的右端点和当前最大右端点进行比较
得到的最大右端点如果仍小于 start,说明没有包含 start 的区间,break,res = -1
得到的最大右端点如果大于 end,那么表示整个区间都被表示了,可以退出
注意: \color{red}{注意:} 注 意 : 因为可能存在
1 5
2
-1 2
2 4
这种情况,即待表示的区间右端点 5 大于目前拥有的区间的最大右端点,所以需要再大于 end 的情况下加一个判断,表示走到了结尾
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 #include <bits/stdc++.h> using namespace std;const int N = 1e5 + 10 ;struct edges { int l, r; bool operator <(const edges&W)const { return l < W.l; } }e[N]; int n, s, t, res;int main () { cin >> s >> t; cin >> n; for (int i = 0 ; i < n; i++) cin >> e[i].l >> e[i].r; sort (e, e + n); bool success = false ; for (int i = 0 ; i < n; i++) { int j = i, st= -2e9 ; while (j < n && e[j].l <= s) { st = max (st, e[j].r); j++; } if (st < s) { res = -1 ; break ; } res++; if (st >= t) { success = true ; break ; } s = st; i = j - 1 ; } if (!success) res = -1 ; cout << res; return 0 ; }