C++常见算法&数据结构模版

发布时间 2023-10-01 19:30:29作者: yhx0322

各种常见算法 & 数据结构模板

1. 最长不下降子序列(LIS)

1.1 \(O(n^2)\) 做法

点击查看代码
for (int i = 1;i <= n;i++) {
		cin >> a[i];
		dp[i] = 1;
	}
	for (int i = 1;i <= n;i++) {
		for (int j = 1;j < i;j++) {
			if (a[i] > a[j]) {
				dp[i] = max(dp[i],dp[j] + 1);
			}
		}
		ans = max(ans,dp[i]);
	}

1.2 \(O(n \log n)\) 做法

点击查看代码
#include <bits/stdc++.h>

using namespace std;

const int N = 1e5 + 10;
int dp[N], n, x, k = 1;
int main() {
    cin >> n >> x;
    dp[1] = x;
    for (int i = 2; i <= n; i++) {
        scanf("%d",&x);
        if (x > dp[k]) {
            dp[++k] = x;
        } else {
            int p = lower_bound(dp + 1, dp + k + 1, x) - dp;
            dp[p] = x;
        }
    }
    cout << k;
    return 0;
}

2. 背包问题

2.1 01背包-二维数组

点击查看代码
	cin >> maxw >> n;
	for (int i = 1;i <= n;i++) {
		cin >> w >> v;
		for (int j = 1;j <= maxw;j++) {
			if (w > j) {
				dp[i][j] = dp[i - 1][j];
			} else {
				dp[i][j] = max(dp[i - 1][j],dp[i - 1][j - w] + v);
			}
		}
	}
	cout << dp[n][maxw];

2.2 01背包-一维数组优化

点击查看代码
	cin >> maxv >> n; // maxv: 价值
	for (int i = 1;i <= n;i++) {
		cin >> v;
		for (int j = maxv;j >= v;j--) {
			dp[j] = max(dp[j],dp[j - v] + v);
		}
	}
	cout << dp[maxv];

2.3 完全背包-一维数组优化

点击查看代码
#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int N = 1e4 + 10, S = 1e7 + 10;
ll dp[S], vi, wi, maxw, n;
int main() {
    scanf("%lld%lld", &maxw, &n);
    for (int i = 1; i <= n; i++) {
        scanf("%lld%lld", &wi, &vi);
        for (int j = wi; j <= maxw; j++) {
            dp[j] = max(dp[j], dp[j - wi] + vi);
        }
    }
    printf("%lld", dp[maxw]);
    return 0;
}

2.4 多重背包-普通版本

点击查看代码
#include <iostream>

using namespace std;

const int N = 110;
int dp[N], v, s, w;
int n, maxw;
int main() {
    scanf("%d%d", &n, &maxw);
    for (int i = 1; i <= n; i++) {
        scanf("%d%d%d", &w, &v, &s);
        for (int k = 1; k <= s; k++) {
            for (int j = maxw; j >= w; j--) {
                dp[j] = max(dp[j], dp[j - w] + v);
            }
        }
    }
    printf("%d", dp[maxw]);
    return 0;
}

2.5 多重背包-二进制优化版

点击查看代码
#include <iostream>

using namespace std;

const int N = 2e4 + 10;
int dp[2010], v[N], w[N];
int n, wi, vi, si, maxw, k;
int main() {
    scanf("%d%d", &n, &maxw);
    for (int i = 1; i <= n; i++) {
        scanf("%d%d%d", &wi, &vi, &si);
        for (int t = 1; t <= si; t *= 2) {
            k++;
            v[k] = t * vi;
            w[k] = t * wi;
            si -= t;
        }
        if (si > 0) {
            k++;
            v[k] = si * vi;
            w[k] = si * wi;
        }
    }
    for (int i = 1; i <= k; i++) {
        for (int j = maxw; j >= w[i]; j--) {
            dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
        }
    }
    printf("%d", dp[maxw]);
    return 0;
}

2.6 分组背包

点击查看代码
#include <iostream>

using namespace std;

const int N = 110;
int dp[N], v[N][N], w[N][N], s[N];
int n, maxw;
int main() {
    scanf("%d%d", &n, &maxw);
    for (int i = 1; i <= n; i++) {
        scanf("%d", &s[i]);
        for (int j = 1; j <= s[i]; j++) {
            scanf("%d%d", &w[i][j], &v[i][j]);
        }
    }
    for (int i = 1; i <= n; i++) {
        for (int j = maxw; j >= 1; j--) {
            for (int k = 1; k <= s[i]; k++) {
                if (w[i][k] <= j) 
                    dp[j] = max(dp[j], dp[j - w[i][k]] + v[i][k]);
                
            }
        }
    }
    printf("%d", dp[maxw]);
    return 0;
}

2.7 二维费用背包

点击查看代码
#include <iostream>

using namespace std;

const int N = 410;
int dp[N][N], maxw, maxv, vi, wi, c;
int n;
int main() {
    scanf("%d%d", &maxw, &maxv);
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> wi >> vi >> c;
        for (int j = maxw; j >= wi; j--) {
            for (int k = maxv; k >= vi; k--) {
                dp[j][k] = max(dp[j][k], dp[j - wi][k - vi] + c);
            }
        }
    }
    cout << dp[maxw][maxv];
    
    return 0;
}

3. 图论最短路

3.1 朴素Dijkstra

时间复杂度:\(O(n^2)\)

点击查看代码
#include <bits/stdc++.h>

using namespace std;

const int N = 60;
int a[N][N];
int d[N];
bool f[N];
int n,s;
int main() {
	cin >> n >> s;
	for (int i = 1;i <= n;i++) {
		for (int j = 1;j <= n;j++) {
			cin >> a[i][j];
		}
	}
	memset(d,0x3f,sizeof(d)); // 初始化为INF
	d[s] = 0; // 走到起点的最短路为0
	for (int i = 1;i <= n;i++) {
		int mi = -1; 
      		// 找到最小数下标
		for (int j = 1;j <= n;j++) {
			if (!f[j] && (mi == -1 || d[j] < d[mi])) {
				mi = j;
			}
		}
		f[mi] = true;
      		// 从最短路的点开始更新最短路
		for (int j = 1;j <= n;j++) {
			if (!f[j] && a[mi][j] && a[mi][j] + d[mi] < d[j]) {
				d[j] = a[mi][j] + d[mi];
			}
		}
		
	}
	for (int i = 1;i <= n;i++) {
		if (i != s) {
          		//走不到 -> 0x3f3f3f3f
			if (d[i] == 0x3f3f3f3f) cout << -1 << " ";
			else cout << d[i] << " ";
		}
	}
	return 0;
}

3.2 优先队列BFS优化 Dijkstra

时间复杂度:\(O(m \times logm)\)

点击查看代码
#include <bits/stdc++.h>

using namespace std;

const int N = 1e5 + 10;
typedef pair<int,int> PII;
struct node{
    int to,next,len;
}a[N];
int pre[N],k = 0;
int n,m,x,y,len;
priority_queue<PII,vector<PII>,greater<PII>> q; // 优先队列
void add(int x,int y,int len) { // 建边
    a[++k] = {y,pre[x],len};
    pre[x] = k;
}
bool f[N];
int d[N];
int main() {
    scanf("%d%d",&n,&m);
    for (int i = 1;i <= m;i++) {
        scanf("%d%d%d",&x,&y,&len);
        add(x,y,len);
    }
    // 初始化
    q.push({0,1});
    memset(d,0x3f,sizeof(d));
    d[1] = 0;
    
    while (!q.empty()) {
        PII h = q.top();
        int dis = h.first,p = h.second;
        q.pop();
        if (f[p]) continue;
        f[p] = true;
        if (p == n) { // 走到终点
            cout << dis;
            return 0;
        }
        for (int i = pre[p];i;i = a[i].next) { // 更新最短路
            int to = a[i].to;
            if (a[i].len + dis < d[to]) {
                d[to] = a[i].len + dis;
                q.push({a[i].len + dis,to});
            }
        }
    }
    cout << -1;
    return 0;
}

3.3 Floyd

时间复杂度:\(O(n^3)\)

点击查看代码
#include <bits/stdc++.h>

using namespace std;


const int N = 110,INF = 0x3f3f3f3f;
int d[N][N];
int n,m,x,y,l;
int main() {
	cin >> n >> m;
	memset(d,0x3f,sizeof(d));
	for (int i = 1;i <= n;i++) d[i][i] = 0;
	
	for (int i = 1;i <= m;i++) {
		cin >> x >> y >> l;
		d[x][y] = l;
	}
	for (int k = 1;k <= n;k++) {
		for (int i = 1;i <= n;i++) {
			for (int j = 1;j <= n;j++) {
				if (d[i][k] != INF && d[k][j] != INF) {
					d[i][j] = min(d[i][j],d[i][k] + d[k][j]);
				}
			}
		}
	}
	for (int i = 1;i <= n;i++) {
		for (int j = 1;j <= n;j++) {
			if (d[i][j] == INF) cout << "N ";
			else cout << d[i][j] << " ";
		}
		cout << endl;
	}
	return 0;
}

4.树

4.1 树的直径

两次深搜求直径:

  1. 从树上任意找一个点 \(x\) 搜索离 \(x\) 最远的点 \(u\)\(u\) 一定是直径的一个端点;

  2. 从直径的一个端点 \(u\) 开始搜索,搜索出离 \(u\) 最远的点 \(v\)\(v\) 一定是直径的另一个端点,因此 \(uv\) 一定是直径。

点击查看代码
#include <bits/stdc++.h>

using namespace std;

const int N = 110;
int n,x,y;
struct node{
	int to,next;
}a[N * 2];
int pre[N];
int k = 0;
int dep[N];
void add(int x,int y) {
	a[++k] = {y,pre[x]};
	pre[x] = k;
}


void dfs(int x,int fath) {
	dep[x] = dep[fath] + 1;
	for (int i = pre[x];i;i = a[i].next) {
		if (a[i].to != fath) {
			dfs(a[i].to,x);
		}
	}
}

int main() {
	cin >> n;
	for (int i = 1;i <= n - 1;i++) {
		scanf("%d%d",&x,&y);
		add(x,y);
		add(y,x);
	}
	dfs(1,0);
	int x = 1;
  	// 求出离1号点最远的那个点
	for (int i = 2;i <= n;i++) {
		if (dep[i] > dep[x]) x = i;
	}
	dfs(x,0);
	int y = 1;
  	// 找出离x号点最远的点y
	for (int i = 1;i <= n;i++) {
		if (dep[i] > dep[y]) y = i;
	}
	printf("%d",dep[y] - 1);

4.2 树的中心

树的中心指的是,该结点离树中的其他结点,最远距离最近

树的中心满足如下性质:

(1)它一定在某条树的直径上,且到直径的两端的距离差不超过1

(2)即使树有多条直径,但树的中心至多有2个,且均为直径的中点。

求树的中心:由于树的中心一定在直径上,求出直径的两个端点 \(u v\),加深 \(v\) 深度更深。

(1) 如果 \(dep[v]\) 是奇数,爬树 \(dep[v]/2\) 次;

(2) 如果 \(dep[v]\) 是偶数,爬树 \(dep[v]/2-1\) 次,得到两个中心的靠下的一个点,另一个点就是该点的父;

点击查看代码
#include <iostream>

using namespace std;

const int N = 1e5 + 10;
int n,x,y;
struct node{
	int to,next;
}a[N * 2];
int pre[N];
int k = 0;
int dep[N],fa[N];
void add(int x,int y) {
	a[++k] = {y,pre[x]};
	pre[x] = k;
}


void dfs(int x,int fath) {
	dep[x] = dep[fath] + 1;
	fa[x] = fath;
	for (int i = pre[x];i;i = a[i].next) {
		if (a[i].to != fath) {
			dfs(a[i].to,x);
		}
	}
}

int main() {
	cin >> n;
	for (int i = 1;i <= n - 1;i++) {
		scanf("%d%d",&x,&y);
		add(x,y);
		add(y,x);
	}
	dfs(1,0);
	int x = 1;
	for (int i = 2;i <= n;i++) {
		if (dep[i] > dep[x]) x = i;
	}
	dfs(x,0);
	int y = 1;
	for (int i = 1;i <= n;i++) {
		if (dep[i] > dep[y]) y = i;
	}
	if (dep[y] % 2 == 1) {
		int d = dep[y] / 2;
		for (int i = 1;i <= d;i++) {
			y = fa[y];
		}
		printf("%d",y);
	} else {
		int d = dep[y] / 2 - 1;
		for (int i = 1;i <= d;i++) {
			y = fa[y];
		}
		if (y < fa[y]) cout << y << " " << fa[y];
		else cout << fa[y] << " " << y;
	}
	return 0;
}

4.3 树的公共祖先(LCA)

解法一:染色法

点击查看代码
#include <iostream>

using namespace std;

const int N = 1e5 + 10;
int n,x,y;
struct node{
	int to,next;
}a[N * 2];
int pre[N];
int k = 0;
int dep[N],fa[N];
void add(int x,int y) {
	a[++k] = {y,pre[x]};
	pre[x] = k;
}
bool vis[N];

void dfs(int x,int fath) {
	dep[x] = dep[fath] + 1;
	fa[x] = fath;
	for (int i = pre[x];i;i = a[i].next) {
		if (a[i].to != fath) {
			dfs(a[i].to,x);
		}
	}
}

int main() {
	cin >> n;
	int u,v;
	cin >> u >> v;
	for (int i = 1;i <= n - 1;i++) {
		scanf("%d%d",&x,&y);
		add(x,y);
		add(y,x);
	}
	dfs(1,0);
	vis[u] = true;
	while (fa[u] != 0) {
		u = fa[u];
		vis[u] = true;
	}
	while (!vis[v]) {
		v = fa[v];
	}
	cout << v;
	return 0;
}

解法二:两个结点同时向上移动

思路:

(1)求出每个结点的深度;

(2)如果两个结点重叠,那么得知公共祖先;

(3)如果不重叠,选择深度更大的结点,向父元素移动,重复上述过程,直到重叠。

点击查看代码
#include <iostream>

using namespace std;

const int N = 1e5 + 10;
int n,x,y;
struct node{
	int to,next;
}a[N * 2];
int pre[N];
int k = 0;
int dep[N],fa[N];
void add(int x,int y) {
	a[++k] = {y,pre[x]};
	pre[x] = k;
}
bool vis[N];

void dfs(int x,int fath) {
	dep[x] = dep[fath] + 1;
	fa[x] = fath;
	for (int i = pre[x];i;i = a[i].next) {
		if (a[i].to != fath) {
			dfs(a[i].to,x);
		}
	}
}

int main() {
	cin >> n;
	int u,v;
	cin >> u >> v;
	for (int i = 1;i <= n - 1;i++) {
		scanf("%d%d",&x,&y);
		add(x,y);
		add(y,x);
	}
	dfs(1,0);
	while (u != v) {
		if (dep[u] > dep[v]) {
			u = fa[u];
		} else {
			v = fa[v];
		}
	}
	cout << u;
	
	return 0;

4.4 树的重心

求出每个结点删除后的最大子树的大小,并求出该值的最小值,最后循环看哪些树删除后子树大小==所求的最小值,哪些结点就是重心。

点击查看代码
#include <bits/stdc++.h>

using namespace std;

const int N = 1e5 + 10;
int n,x,y;
struct node{
	int to,next;
}a[N * 2];
int pre[N];
int k = 0;
int dep[N],fa[N],si[N];
int ma[N];
int mi = INT_MAX;
void add(int x,int y) {
	a[++k] = {y,pre[x]};
	pre[x] = k;
}
bool vis[N];

int dfs(int x,int fath) {
	dep[x] = dep[fath] + 1;
	fa[x] = fath;
	si[x] = 1;
	for (int i = pre[x];i;i = a[i].next) {
		if (a[i].to != fath) {
			si[x] += dfs(a[i].to,x);
		}
	}
	return si[x];
}

int main() {
	cin >> n;
	for (int i = 1;i <= n - 1;i++) {
		scanf("%d%d",&x,&y);
		add(x,y);
		add(y,x);
	}
	dfs(1,0);
	for (int i = 1;i <= n;i++) {
		ma[i] = n - si[i];
		for (int j = pre[i];j;j = a[j].next) {
			if (a[j].to != fa[i]) {
				ma[i] = max(ma[i],si[a[j].to]);
			}
		}
		mi = min(ma[i],mi);
	}
	for (int i = 1;i <= n;i++) {
		if (ma[i] == mi) {
			cout << i << " ";
		}
	}
	return 0;
}

5.最长公共子序列(LCS)

5.1 \(O(n^2)\) 做法

点击查看代码
#include <iostream>

using namespace std;

const int N = 1010;
int n, dp[N][N], a[N], b[N];
int main() {
    cin >> n;
    for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
    for (int i = 1; i <= n; i++) scanf("%d", &b[i]);
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) {
            if (a[i] == b[j]) {
                dp[i][j] = dp[i - 1][j - 1] + 1;
            } else {
                dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
            }
        }
    }
    printf("%d", dp[n][n]);
    return 0;
}

5.2 \(O(n \log n)\) 做法

点击查看代码
#include <iostream>

using namespace std;

const int N = 1e5 + 10;
int dp[N], a[N], b[N], n, c[N];
int main() {
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
        c[a[i]] = i;
    }
    for (int i = 1; i <= n; i++) {
        cin >> b[i];
    }
    dp[1] = c[b[1]];
    int k = 1;
    for (int i = 2; i <= n; i++) {
        if (c[b[i]] > dp[k]) {
            dp[++k] = c[b[i]];
        } else {
            int p = lower_bound(dp + 1, dp + k + 1, c[b[i]]) - dp;
            dp[p] = c[b[i]];
        }
    }
    printf("%d", k);
    return 0;
}

6. 并查集

6.1 朴素并查集

点击查看代码
int find(int x) {
	if (x == fa[x]) return x;
	return find(fa[x]);
}
点击查看代码
void merge(int x, int y) {
	int rx = find(x), ry = find(y);
	if (rx != ry) fa[rx] = ry;
}

6.2 路径压缩

点击查看代码
int find(int x) {
	if (x == fa[x]) return x;
	return fa[x] = find(fa[x]);
}

7. 最小生成树

7.1 \(Kustral\) 算法

点击查看代码
for (int i = 1;i <= k;i++) {
		if (find(a[i].x) != find(a[i].y)) {
			merge(a[i].x, a[i].y);
			sum += a[i].len;
			c++;
			
			if (c == n - 1) {
				printf("%d", sum);
				return 0;
			}
		}
	}

8. 素数筛法

8.1 埃氏筛法

点击查看代码
#include <bits/stdc++.h>

using namespace std;

const int N = 1e6 + 10;
bool f[N];
int n, ans;
int main() {
	scanf("%d", &n);
	f[0] = f[1] = true;
	for (int i = 2;i <= sqrt(n);i++) {
		if (!f[i]) {
			for (int j = i * 2;j <= n;j += i) {
				if (!f[j]) {
					f[j] = true;
				}
			}
		}
	}
	for (int i = 1; i <= n; i++) {
		if (!f[i]) ans++;
	}
	printf("%d", ans);
	return 0;
}

8.2 欧拉筛法

点击查看代码
f[0] = f[1] = true;
	for (int i = 2;i <= e;i++) {
		if (!f[i]) primes[++k] = i;
		for (int j = 1;i * primes[j] <= e;j++) {
			f[i * primes[j]] = true;
			if (i % primes[j] == 0) break;
		}
	}