图论


建图(链式前向星)

int h[N],e[N], en[N];//这里的en一般写成ne
int idx;
int w[N];
void add(int a, int b, int c)
{
	e[idx] = b;
	en[idx] = h[a];
	w[idx] = c;
	h[a] = idx++;
}

寻找距离r点最远的距离

void dfs(int r,int l)
{
		for (int i = h[r]; i != -1; i = en[i])//遍历r的所有临边
		{
			int q = e[i];
			if (!st[q])
			{
				st[q] = true;
				int co = w[i];
				if (l + co > maxx)
				{
					far = q;
					maxx = l + co;
				}
				dfs(q, l + co);
			}
		}
}

二分图判断

当且仅当图中没有奇数环,在所有连通块中,将随机一个点染色,然后dfs遍历连通块,和这个点相连的点染其他颜色,如果颜色相同,那么失败

bool check=true;
for (int i = 1; i <= n; i++) {
	if (!color[i]) {
	      dfs(i, 1);
	}
}

void dfs(int a, int b) {
	color[a] = b;
	for (int i = h[a]; ~i; i = ne[i]) {
		if (!color[e[i]]) {
			dfs(e[i], 3 - b);//1和2是不同的颜色
		} else if (color[e[i]] == b) {
			check = false;
			return;
		}
	}
}

树遍历

前序遍历:根左右
中序遍历:左根右
后序遍历:左右根
根据前中遍历求得后续遍历(存在ans),当无法找到根节点时表示无法完成,根据build的顺序可以决定存储结果是前中后序列遍历

void build1(int ql, int qr, int zl, int zr) {
	int root = qianxu[ql];
	int k = -1;
	for (int i = zl; i <= zr; i++) {
		if (zhongxu[i] == root) {
			k = i;
			break;
		}
	}
	if (k == -1) {
		st = false;
		return;
	} else {
		int llen = k - zl;
		int rlen = zr - k;
		if (llen > 0) {
			build1(ql + 1, ql + llen, zl, k - 1);//遍历左子树
		}
		if (rlen > 0) {
			build1(qr - rlen + 1, qr, k + 1, zr);//遍历右子树
		}
	}
	ans.push_back(root);//遍历根
}

//结论:对于任何给定的完全二叉树结点数量,都可以通过数组直接给定,结点数量就是数组长度

最小生成树

遍历n次,每次取出距离连通块最近的没有标记过的点,然后标记该点,通过该点的临边更新其他边,返回最小生成树边总长度。

int prim() {
	int ans = 0;
        memset(st,0,sizeof(st));
	memset(dist, 0x3f, sizeof(dist));
	dist[1] = 0;
	for (int i = 1; i <= n; 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;
		ans += dist[t];
		for (int j = 1; j <= n; j++) {
			dist[j] = min(dist[j], an[t][j]);
		}
	}
	return ans;
}

迪杰斯特拉(nlogn):

基本思想:初始化为{0,n},意思为n到n的距离为0,每次取出优先队列中最小的点,也就是可以确定的最短路径和点,根据这个已经确定的点去更新周围的点,但凡是能更新的,都加入优先队列中,st[i]表示第i点是否已经被确认最短路径了,而且最开始n刚加进去的时候还没有被确认,所以一开始别把n认定为确定的点了。

#define x first
#define y second
typedef pair<int, int> pi;
void djs(int n)
{

	memset(dist, 0x3f, sizeof(dist));
        memset(st,false,sizeof(st));
	priority_queue<pi, vector<pi>, greater<pi>> heap;
	dist[n] = 0;
	heap.push({ 0,n });
	while (heap.size())
	{
		auto p = heap.top();
		heap.pop();
		if (st[p.y])
			continue;
		st[p.y] = true;
		for (int u = h[p.y];u != -1; u = ne[u])
		{
			int a = e[u];//这个点
			int dis = w[u];
			if (dist[a] > dist[p.y] + dis)
			{
				dist[a] = dist[p.y] + dis;
				heap.push({dist[a],a});
			}
		}
	}
}

bellman-ford

int dist[510];
struct Edge {
	int a, b, c;
}edge[10010];
int main()
{
	memset(dist, 0x3f, sizeof(dist));
	dist[1] = 0;
	int n, m, k;
	cin >> n >> m >> k;
	for (int i = 1; i <= m; i++)
	{
		cin >> edge[i].a >> edge[i].b >> edge[i].c;
	}
	for (int i = 1; i <= k; i++)
	{
		int distt[510];
		memcpy(distt,dist,sizeof(dist));
		for (int j = 1; j <= m; j++)
		{
			int to = edge[j].b;
			int pp = edge[j].a;
			dist[to] = min(dist[to], distt[pp] + edge[j].c);
		}
	}
	if (dist[n] > 0x3f3f3f3f/2)//重要,比如说5是无穷大,4是无穷大,4-5是-2,那么5会更新为0x3f3f3f3f-2;
		cout << "impossible";
	else
		cout << dist[n];
	return 0;
}

spfa

bellman-ford的优化版本,对于含负权也可以用,最坏 O(nm)最好O(m),m是边数,n是边数
基本思想是:初始加入最初点,st[i]表示i是否在queue中,根据queue里面的点依次更新其他所有相邻点,如果一个点被更新了,那么加入队列,注意出队列时要把st改变。

void spfa() {
	memset(dist, 0x3f, sizeof(dist));
      memset(st,false,sizeof(st));
	q.push(1);
	dist[1] = 0;
	st[1] = true;
	while (q.size()) {
		int a = q.front();
		q.pop();
		st[a]=false;
		for (int i = h[a]; i != -1; i = ne[i]) {
			if (dist[e[i]] > dist[a] + w[i]) {
				dist[e[i]] = dist[a] + w[i];
				if (!st[e[i]]) {
					q.push(e[i]);
					st[e[i]]=true;
				}
			}
		}
	}
}

无向图的最小环(大于等于三个节点):

在floyd的第k层未开始循环前,已经求解了所有i和j之间经过1k-1的最短路径,为了按照环中的最大点来分类,枚举点k的所有临点i和j(i和j均小于k),如果i和j已经可达,那么i和j之间的点也只是1k-1,满足环中最大点为k,此时环的值为k的两个临边和加上i和j的最短路径长度。
为了按顺序输出方案,可以把方案定为kiget_path(i,j)~j,这里的get_path是i和j的最短路中所有点(不包括i和j),在floyd循环中可以记录最后一个更新i和j距离的点,记为pos[i][j],那么get_path(i,j)可以用分治法,即get_path(i,j)=get_path(i,pos[i][j])+pos[i][j]+get_path(pos[i][j],j),当pos[i][j]==0时意味着i和j的最短路不经过其他点,那么按照get_path(i,j)的定义”i和j的最短路中所有点(不包括i和j)“,就可以直接返回了。

#include<bits/stdc++.h>
using namespace std;
const int INF = 1e9;
typedef long long ll;
int w[110][110], f[110][110];
vector<int> path;
int pos[110][110];
void get_path(int i, int j) {
	if (pos[i][j] == 0)
		return;
	get_path(i, pos[i][j]);
	path.push_back(pos[i][j]);
	get_path(pos[i][j], j);
}
int main(void) {
	int n, m;
	cin >> n >> m;
	memset(w, 0x3f, sizeof(w));
	for (int i = 1; i <= m; i++) {
		int a, b, c;
		cin >> a >> b >> c;
		w[a][b] = min(w[a][b], c);
		w[b][a] = min(w[b][a], c);
	}
	for (int i = 1; i <= n; i++)
		w[i][i] = 0;
	memcpy(f, w, sizeof(w));
	int ans = INF;
	for (int k = 1; k <= n; k++) {
		for (int i = 1; i < k; i++) {
			for (int j = i + 1; j < k; j++) {
				if ((ll)w[i][k] + w[k][j] + f[i][j] < ans) {
					{
						ans = w[i][k] + w[k][j] + f[i][j];
						path.clear();
						path.push_back(k);
						path.push_back(i);
						get_path(i, j);
						path.push_back(j);
					}

				}
			}
		}
		for (int i = 1; i <= n; i++) {
			for (int j = 1; j <= n; j++) {
				{
					if (f[i][k] + f[k][j] < f[i][j]) {
						f[i][j] = f[i][k] + f[k][j];
						pos[i][j] = k;
					}
				}

			}
		}
	}
	if(path.size())
	{
	    for (auto i : path)
		cout << i << " ";
	}
	else
	cout<<"No solution.\n";
	return 0;
}

最短路计数

按照djs来求解过程一定是拓扑序。如果f+w<f,那么最短路数量前赋予给后,如果f+w和f相同那么最短路数量后+前

void djs(int a) {
	memset(st, false, sizeof(st));
	memset(f, 0x3f, sizeof(f));
	priority_queue<pi, vector<pi>, greater<pi>> heap;
	heap.push({0, a});
	f[a] = 0;
	dp[a] = 1;
	while (heap.size()) {
		pi u = heap.top();
		heap.pop();
		if (st[u.y])
			continue;
		st[u.y] = true;
		for (int i = h[u.y]; ~i; i = ne[i]) {
			if (u.x + w[i] < f[e[i]]) {
				f[e[i]] = u.x + w[i];
				heap.push({f[e[i]], e[i]});
				dp[e[i]] = dp[u.y];//第一次遇到最短路,覆盖掉最短路
			} else if (u.x + w[i] == f[e[i]]) {
				dp[e[i]] =(dp[e[i]]+ dp[u.y]);//相当于dp
			}
		}
	}
}

最短路计数拓展

求每个点的最短路和次短路数量
思路为在djs过程中将每个点分为两部分,f[i][0]是i的最短路长度,f[i][1]是到i的次短路长度,cnt[i][0]是到i的最短路数量,cnt[i][1]是到i的次短路数量。
heap中弹出来的为点+状态的二维表示状态,所以判断是否出现也是点+状态的二维表示,f[u][type]+w[i]有四种情况,是被f[k][0],f[k][1]隔开的,分别是小于f[k][0],类比与树形dp,次小值继承前一个最小值的信息;然后是等于f[k][0]和f[k][1],然后是大于f[k][0]小于f[k][1],注意每一次更新都需要将更新后的二维状态加入heap数组。

struct node {
	int dist, u, type;
	bool operator>(const node a) const {
		return dist > a.dist;
	}
};

void djs(int s) {
	memset(f, 0x3f, sizeof(f));
	memset(st, 0, sizeof(st));
	memset(cnt, 0, sizeof(cnt));
	priority_queue<node, vector<node>, greater<node>> heap;
	heap.push({0, s, 0});
	f[s][0] = 0;
	cnt[s][0] = 1;
	cnt[s][1] = 0;
	while (heap.size()) {
		node l = heap.top();
		heap.pop();
		int u = l.u, dist = l.dist, type = l.type;
		if (st[u][type])
			continue;
		st[u][type] = true;
		for (int i = h[u]; ~i; i = ne[i]) {
			int k = e[i];
			if (dist + w[i] < f[k][0]) {
				f[k][1] = f[k][0], cnt[k][1] = cnt[k][0];
				heap.push({f[k][1], k, 1});
				f[k][0] = dist + w[i], cnt[k][0] = cnt[u][type];
				heap.push({f[k][0], k, 0});
			} else if (dist + w[i] == f[k][0])
				cnt[k][0] += cnt[u][type];
			else if (dist + w[i] < f[k][1]) {
				f[k][1] = dist + w[i], cnt[k][1] = cnt[u][type];
				heap.push({f[k][1], k, 1});
			} else if (dist + w[i] == f[k][1])
				cnt[k][1] += cnt[u][type];
		}
	}
}

文章作者: ycx
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 ycx !
评论
 上一篇
算法小技巧 算法小技巧
对于算法初学者来说,一些实用的函数可以帮助节省不少时间
2024-07-27
下一篇 
数学 数学
算法中常见的数学知识,这里只总结本人在各种题库中遇见频率较高且实用的算法。
2024-07-27
  目录