NOTE: 主要是学习期间的代码整理,y总算法基础课
KMP
code
#include<iostream>
using namespace std;
const int N = 10010, M = 100010;
int n, m;
char p[N], s[M];
int ne[N];
int main()
{
cin >> n >> p + 1 >> m >> s + 1;
// 求 next
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;
}
// kmp 匹配过程
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){
// success
cout << i - n << " ";
j = ne[j];
}
}
return 0;
}
搜索与图论 — 求最短路径的问题
朴素Dijkstra算法
思想
- 权重都为正,O(n)
- 849. Dijkstra求最短路 I - AcWing题库
code
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 510;
// 朴素 Dijkstra 迪杰斯特拉算法 O(n ^ 2) 邻接矩阵 稠密图 n m 相差很大
int n, m;
int g[N][N];
int dist[N];
bool st[N];
int dijkstra()
{
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
for(int i = 0; i < n; i++){
int t = -1;
for(int j = 1; j <= n; j++){
if(!st[j] && (t == -1 || dist[t] > dist[j])){
t = j;
}
}
st[t] = true;
if(t == n) break;
for(int j = 1; j <= n; j++){
dist[j] = min(dist[j], dist[t] + g[t][j]);
}
}
if(dist[n] == 0x3f3f3f3f) return -1;
else return dist[n];
}
int main()
{
cin >> n >> m;
memset(g, 0x3f, sizeof g);
while(m--){
int a, b, c;
cin >> a >> b >> c;
g[a][b] = min(g[a][b], c);
}
int t = dijkstra();
cout << t << endl;
return 0;
}
堆优化的迪杰斯特拉算法
思想
-
权重都为正,O(mlogn)
code
#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int N = 1000010;
typedef pair<int, int> PII;
// 堆优化的迪杰斯特拉算法 o(mlogn) 稀疏图(n,m 相差不大) 邻接表
int n, m;
int h[N], w[N], e[N], ne[N], idx;
int dist[N];
bool st[N];
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
}
int dijkstra()
{
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
priority_queue<PII, vector<PII>, greater<PII> > heap;
heap.push({0, 1});
while(heap.size()){
auto t = heap.top();
heap.pop();
int ver = t.second, distance = t.first;
if(st[ver]) continue;
st[ver] = true;
for(int i = h[ver]; i != -1; i = ne[i]){
int j = e[i];
if(dist[j] > distance + w[i]){
dist[j] = distance + w[i];
heap.push({dist[j], j});
}
}
}
if(dist[n] == 0x3f3f3f3f) return -1;
else return dist[n];
}
int main()
{
cin >> n >> m;
memset(h, -1, sizeof h);
while(m--){
int a, b, c;
cin >> a >> b >> c;
add(a, b, c);
}
int t = dijkstra();
cout << t << endl;
return 0;
}
Bellman-Ford算法
思想
- 权重可能为负,O(nm)
- 853. 有边数限制的最短路 - AcWing题库
code
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 510, M = 10010;
int n, m, k;
int dist[N], backup[N];
struct Edge
{
int a, b, w;
}edges[M];
void bellman_ford()
{
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
for(int i = 0; i < k; i++){
memcpy(backup, dist, sizeof dist); // copy
for(int j = 0; j < m; j++){
int a = edges[j].a, b = edges[j].b, w = edges[j].w;
dist[b] = min(dist[b], backup[a] + w);
}
}
if(dist[n] > 0x3f3f3f3f / 2) puts("impossible");
else cout << dist[n] << endl;
}
int main()
{
cin >> n >> m >> k;
for(int i = 0; i < m; i++){
int a, b, w;
cin >> a >> b >> w;
edges[i] = {a, b ,w};
}
bellman_ford();
return 0;
}
SPFA 算法
思想
- 权重可能为负,一般是 O(n)
- 851. spfa求最短路 - AcWing题库
- 852. spfa判断负环 - AcWing题库
code - 求最短路
#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int N = 1000010;
int n, m;
int h[N], w[N], e[N], ne[N], idx;
int dist[N];
bool st[N];
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
}
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 = h[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;
}
}
}
}
if(dist[n] == 0x3f3f3f3f) puts("impossible");
else cout << dist[n] << endl;
}
int main()
{
cin >> n >> m;
memset(h, -1, sizeof h);
while(m--){
int a, b, c;
cin >> a >> b >> c;
add(a, b, c);
}
spfa();
return 0;
}
code - 判断负环
#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int N = 1000010;
int n, m;
int h[N], w[N], e[N], ne[N], idx;
int dist[N];
bool st[N];
int cnt[N];
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
}
bool spfa()
{
queue<int>q;
for(int i = 1; i <= n; i++){
st[i] = true;
q.push(i);
}
while(q.size())
{
int t = q.front();
q.pop();
st[t] = false;
for(int i = h[t]; i != -1; i = ne[i]){
int j = e[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()
{
cin >> n >> m;
memset(h, -1, sizeof h);
while(m--){
int a, b, c;
cin >> a >> b >> c;
add(a, b, c);
}
if(spfa()) puts("Yes");
else puts("No");
return 0;
}
Floyd 算法
思想
- 多重路径,三for,O(n3)
- 854. Floyd求最短路 - AcWing题库
code
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 210, INF = 1e9;
int n, m, Q;
int d[N][N];
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]);
}
}
}
}
int main()
{
cin >> n >> m >> Q;
for(int i = 1; i <= n; i++){
for(int j = 1; j <= n; j++){
if(i == j) d[i][j] = 0;
else d[i][j] = INF;
}
}
while(m--){
int a, b, w;
cin >> a >> b >> w;
d[a][b] = min(d[a][b], w);
}
floyd();
while(Q--){
int a, b;
cin >> a >> b;
if(d[a][b] > INF / 2) cout << "impossible" << endl;
else cout << d[a][b] << endl;
}
return 0;
}
最小生成树
普利姆算法(Prim)
朴素版本
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 510, INF = 0x3f3f3f3f;
int n, m;
int g[N][N];
int dist[N];
bool st[N];
int prim()
{
memset(dist, 0x3f, sizeof dist);
int res = 0;
for(int i = 0; i < n; i++){
int t = -1;
for(int j = 1; j <= n; j++){
if(!st[j] && (t == -1 || dist[t] > dist[j])){
t = j;
}
}
if(i && dist[t] == INF) return INF;
if(i) res += dist[t];
for(int j = 1; j <= n; j++){
dist[j] = min(dist[j], g[t][j]);
}
st[t] = true;
}
return res;
}
int main()
{
cin >> n >> m;
memset(g, 0x3f, sizeof g);
while(m--){
int a, b, c;
cin >> a >> b >> c;
g[a][b] = g[b][a] = min(g[a][b], c);
}
int t = prim();
if(t == INF) cout << "impossible" << endl;
else cout << t << endl;
return 0;
}
堆优化版本(不常用)
- O(mlogn)
克鲁斯卡尔算法(Kruskal)
- O(mlogm)
- 859. Kruskal算法求最小生成树 - AcWing题库
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 200010;
int n, m;
int p[N];
struct Edge
{
int a, b, w;
bool operator<(const Edge &W)const{
return w < W.w;
}
}edges[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 = 0; i < m; i++){
int a, b, w;
cin >> a >> b >> w;
edges[i] = {a, b, w};
}
sort(edges, edges + m);
for(int i = 1; i <= n; i++){
p[i] = i;
}
int res = 0, cnt = 0;
for(int i = 0; i < m; i++){
int a = edges[i].a, b = edges[i].b, w = edges[i].w;
a = find(a), b = find(b);
if(a != b){
p[a] = b;
res += w;
cnt ++;
}
}
if(cnt < n - 1) cout << "impossible" << endl;
else cout << res << endl;
return 0;
}
二分图
- 二分图:当且仅当图中不含有奇数环
染色法
-
O(n + m)
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 100010, M = 200010;
int n, m;
int h[N], e[M], ne[M], idx;
int color[N];
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
bool dfs(int u, int c)
{
color[u] = c;
for(int i = h[u]; i != -1; i = ne[i]){
int j = e[i];
if(!color[j]){
if(!dfs(j, 3 - c)) return false;
}else if(color[j] == c) return false;
}
return true;
}
int main()
{
cin >> n >> m;
memset(h, -1, sizeof h);
while(m--){
int a, b;
cin >> a >> b;
add(a, b), add(b, a);
}
bool flag = true;
for(int i = 1; i <= n; i++){
if(!color[i]){
if(!dfs(i, 1)){
flag = false;
break;
}
}
}
if(flag) cout << "Yes" << endl;
else cout << "No" << endl;
return 0;
}
匈牙利算法
-
O(mn),实际远小于O(mn)
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 510, M = 100010;
int n1, n2, m;
int h[N], e[M], ne[M], idx;
int match[N];
bool st[N];
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}
bool find(int x)
{
for(int i = h[x]; i != -1; i = ne[i]){
int j = e[i];
if(!st[j]){
st[j] = true;
if(match[j] == 0|| find(match[j])){
match[j] = x;
return true;
}
}
}
return false;
}
int main()
{
cin >> n1 >> n2 >> m;
memset(h, -1, sizeof h);
while(m--){
int a, b;
cin >> a >> b;
add(a, b);
}
int res = 0;
for(int i = 1; i <= n1; i++){
memset(st, false, sizeof st);
if(find(i)) res++;
}
cout << res << endl;
return 0;
}
背包问题
01背包问题
思想
-
每个物品只能选一个
-
f(i,j)
从前i
个中选,总体积不超过j
的最大价值 -
不含
i
:f(i-1, j)
含 i :f(i-1,j-Vi) +Wi
-
f(i,j) = MAX(f(i-1,j),f(i-1,j-Vi)+Wi)
code
// 二维
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1010;
int n, m;
int v[N], w[N];
int f[N][N];
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 = 0; j <= m; j++){
f[i][j] = f[i - 1][j];
if(j >= v[i]) f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + w[i]);
}
}
cout << f[n][m] << endl;
return 0;
}
- 优化成一维
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1010;
int n, m;
int v[N], w[N];
int f[N];
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 = m; j >= v[i]; j--){
f[j] = max(f[j], f[j - v[i]] + w[i]);
}
}
cout << f[m] << endl;
return 0;
}
完全背包问题
思想
-
每件物品有无限件可以用
-
f[i,j] = f[i-1,j-v[i]*k]+w[i]*k k --> (0,1,2)
code
- 朴素
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1010;
int n, m;
int v[N], w[N];
int f[N][N];
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 = 0; j <= m; j++){
f[i][j] = f[i - 1][j];
if(j >= v[i]) f[i][j] = max(f[i][j], f[i][j - v[i]] + w[i]);
}
}
cout << f[n][m] << endl;
return 0;
}
- 优化
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1010;
int n, m;
int v[N], w[N];
int f[N];
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] << endl;
return 0;
}
多重背包问题
思想
-
每个物品有限制最多选几个
code
- 朴素
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 110;
int n, m;
int v[N], w[N], s[N];
int f[N][N];
int main()
{
cin >> n >> m;
for(int i = 1; i <= n; i++){
cin >> v[i] >> w[i] >> s[i];
}
for(int i = 1; i <= n; i++){
for(int j = 0; j <= m; j++){
for(int k = 0; k <= s[i] && k * v[i] <= j; k++){
f[i][j] = max(f[i][j], f[i - 1][j - v[i] * k] + w[i] * k);
}
}
}
cout << f[n][m] << endl;
return 0;
}
二进制优化
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 25000;
int n, m;
int v[N], w[N];
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;
// 拆分成 1, 2, 4, 8, 16, ....
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;
// 做 01背包问题
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] << endl;
return 0;
}
分组背包问题
思想
-
物品在不同的组别,同一组的物品最多选一个
code
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 110;
int n, m;
int v[N][N], w[N][N], s[N];
int f[N];
int main()
{
cin >> n >> m;
for(int i = 1; i <= n; i++){
cin >> s[i];
for(int j = 0; 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] << endl;
return 0;
}
动态规划
线性 DP
数字三角形
-
f[i,j]
所有从起点走到(i,j)
的路径
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 510, INF = 1e9;
int n;
int a[N][N];
int f[N][N];
int main()
{
cin >> n;
for(int i = 1; i <= n; i++){
for(int j = 1; j <= i; j++){
cin >> a[i][j];
}
}
for(int i = 0; i <= n; i++){
for(int j = 0; j <= i + 1; j++){
f[i][j] = -INF;
}
}
f[1][1] = a[1][1];
for(int i = 2; i <= n; i++){
for(int j = 1; j <= i; j++){
f[i][j] = max(f[i - 1][j - 1] + a[i][j], f[i - 1][j] + a[i][j]);
}
}
int res = -INF;
for(int i = 1; i <= n; i++){
res = max(res, f[n][i]);
}
cout << res << endl;
return 0;
}
最长上升子序列
朴素版本
- 895. 最长上升子序列 - AcWing题库
f[i]
所有以第i
个数结尾的上升子序列,O(n * n)
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1010;
int n;
int a[N], f[N];
int main()
{
cin >> n;
for(int i = 1; i <= n; i++) cin >> a[i];
for(int i = 1; i <= n; i++){
f[i] = 1;
for(int j = 1; j < i; j++){
if(a[j] < a[i]){
f[i] = max(f[i], f[j] + 1);
}
}
}
int res = 0;
for(int i = 1; i <= n; i++){
res = max(res, f[i]);
}
cout << res << endl;
return 0;
}
优化版本
最长公共子序列
-
f[i, j]
:所有在第一个序列的前i
个字母中出现,且在第二个序列的前j
个字母中出现的公共子序列
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1010;
int n, m;
char a[N], b[N];
int f[N][N];
int main()
{
cin >> n >> m;
scanf("%s%s", 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] << endl;
return 0;
}
最短编辑距离
区间 DP
石子合并
-
f[i,j]
:所有将第i
堆 到第j
堆石子合并成一堆石子的所有方式,O(n * n * n)
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 310;
int n;
int s[N];
int f[N][N];
int main()
{
cin >> n;
for(int i = 1; i <= n; i++) cin >> s[i];
for(int i = 1; i <= n; i++) s[i] += s[i - 1];
for(int len = 2; len <= n; len ++){
for(int i = 1; i + len - 1 <= n; i++){
int l = i, r = i + len - 1;
f[l][r] = 1e8;
for(int k = l; k < r; k++){
f[l][r] = min(f[l][r], f[l][k] + f[k + 1][r] + s[r] - s[l - 1]);
}
}
}
cout << f[1][n] << endl;
return 0;
}
数位统计 DP
计数问题
-
分类讨论 :求第 i 位是 x 的情况的个数,再求和所有位
-
再好好思考一下
#include<iostream>
#include<algorithm>
using namespace std;
int get(vector<int> num, int l, int r)
{
int res = 0;
for(int i = l; i >= r; i--){
res = res * 10 + num[i];
}
return res;
}
int power10(int x)
{
int res = 1;
while(x--){
res *= 10;
}
return res;
}
int count(int n, int x)
{
if(!n) return 0;
vector<int> num;
while(n){
num.push_back(n % 10);
n /= 10;
}
n = num.size();
int res = 0;
for(int i = n - 1 - !x; i >= 0; i--){
if(i < n - 1){
res += get(num, n - 1, i + 1) * power10(i);
if(!x) res -= power10(i);
}
if(num[i] == x){
res += get(num, i - 1, 0) + 1;
}else if(num[i] > x){
res += power10(i);
}
}
return res;
}
int main()
{
int a, b;
while(cin >> a >> b, a || b){
if(a > b) swap(a, b);
for(int i = 0; i < 10; i++){
cout << count(b, i) - count(a - 1, i) << " ";
}
cout << endl;
}
return 0;
}