图论算法模板

最小生成树(克鲁斯卡尔)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2e5 + 100;
struct node
{
	int	u, v, w;
}e[maxn];
int fa[maxn];
int n, m;
int ans;
int cnt;
bool cmp(node a, node b)
{
	return a.w < b.w;
}
int find(int x)
{
	if (x == fa[x])return x;
	return fa[x] = find(fa[x]);
}
void kruskal()
{
	sort(e + 1, e + 1 + m, cmp);
	for (int i = 1; i <= m; i++)
	{
		int fu = find(e[i].u);
		int fv = find(e[i].v);
		if (fu == fv)continue;
		ans += e[i].w;
		fa[fu] = fv;
		if (++cnt == n - 1)break;
	}
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	cin >> n >> m;
	for (int i = 1; i <= n; i++)fa[i] = i;
	for (int i = 1; i <= m; i++)cin >> e[i].u >> e[i].v >> e[i].w;
	kruskal();
	if (cnt == n - 1)cout << ans << endl;
	else cout << "orz" << endl;

	return 0;
}

二分图最大匹配(匈牙利算法)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n;
int a[10004];
int vis[10004];
int pos[10004];
int edg[10004][10004];
bool dfs(int x)
{
	for (int j = 1; j <= n; j++)
	{
		if (!vis[j] && edg[x][j])
		{
			vis[j] = 1;
			if (pos[j] == 0 || dfs(pos[j]))
			{
				pos[j] = x;
				return 1;
			}
		}
	}return 0;
}
int hungarian()
{
	int ans = 0;
	for (int i = 1; i <= n; i++)
	{
		memset(vis, 0, sizeof(vis));
		if (dfs(i))ans++;
	}return ans;
}
int main()
{
	ios::sync_with_stdio(false);
	cin >> n;
	for (int i = 1; i <= n; i++)cin >> a[i];
	for (int i = 1; i <= n-1; i++)
	{
		for (int j = i + 1; j <= n; j++)
		{
			edg[a[i]][a[j]] = 1;          
			//建立边视具体题目而定,故以下操作略
		}
	}
	return 0;
}

单源最短路(dijkstra)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 100;
int dis[maxn];
int pre[maxn];
int n, m, x, y, z, aim;
//输入n和m,代表n个节点,m条边,然后是m行输入,
//每行有x,y,z,代表x到y的路距离为z,aim为终点。
struct edge
{
	int to; //终点
	int cost; //权重
	edge(int x, int y) :to(x), cost(y) {};
};
typedef pair<int, int>P; //first为最短距离,second为顶点编号
vector<edge> graph[maxn]; //邻接表
void dijkstra(int s)
{
	//初始化
	memset(pre, -1, sizeof(pre));
	memset(dis, 0x3f, sizeof(dis));//距离初始化为无穷大
	dis[s] = 0;//自身到自身的距离为0

	//默认对pair.first的大小进行排序 从小到大
	priority_queue<P, vector<P>, greater<P> >que;

	que.push(P(0, s));
	while (!que.empty())
	{
		P p = que.top();
		que.pop();
		//顶点编号
		int v = p.second;
		//dist[v]经过松弛操作变小,压入堆中的路径失去价值
		if (dis[v] < p.first)continue;
		//利用最短边进行松弛
		for (int i = 0; i < graph[v].size(); i++)
		{
			edge e = graph[v][i];
			if (dis[e.to] > dis[v] + e.cost) {
				dis[e.to] = dis[v] + e.cost;
				que.push(P(dis[e.to], e.to));
				pre[e.to] = v;
			}
		}
	}
}
void recout(int x)
{
	if (x == -1)return;
	//递归输出
	recout(pre[x]);
	cout << x << "->";
}
int main()
{
	ios::sync_with_stdio(false);
	cin >> n >> m;
	for (int i = 1; i <= m; i++)
	{
		cin >> x >> y >> z;
		graph[x].push_back(edge(y, z)); //起点x终点y权重w
	}
	//起点为1时
	dijkstra(1);
	//终点aim
	cin >> aim;
	recout(pre[aim]);
	cout << aim << endl;
	//打印到aim的最短距离
	cout << dis[aim] << endl;
	return 0;
}

单源最短路(spfa)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 200010;
struct edg
{
	int to, nextnum, w;
 //to为终点,nextnum为这条边的起点所连的下一条边的边序号,w为权重
}e[maxn];
int n, m;
int edgnum[maxn]; //当前起点的多条边的最后一条的编号
int dis[maxn];
bool vis[maxn];
int cnt[maxn];
int num;

void add(int x, int y, int z)
{
	e[++num].to = y;
	e[num].nextnum = edgnum[x];
	e[num].w = z;
	edgnum[x] = num;
	return;
}

bool spfa(int s)
{
	queue<int>q;
	memset(vis, 0, sizeof(vis));
	memset(dis, 0x3f, sizeof(dis));
	memset(cnt, 0, sizeof(cnt));

	q.push(s);
	vis[s] = 1;
	dis[s] = 0;
	cnt[s]++;

	while (!q.empty())
	{
		int qf = q.front();
		q.pop();
		vis[qf] = 0;
		for (int i = edgnum[qf]; i; i = e[i].nextnum)
		{
			int temp;
			temp = e[i].to;
			if (dis[temp] > dis[qf] + e[i].w)
			{
				dis[temp] = dis[qf] + e[i].w;
				if (!vis[temp])
				{
					q.push(temp);
					cnt[temp]++;
					vis[temp] = 1;
					if (cnt[temp] > n)return 0;
				}
			}
		}
	}
	return 1;
}
int main()
{
	ios::sync_with_stdio(false);
	int n, m, S, T;
	cin >> n >> m >> S >> T;
	for (int i = 1; i <= m; i++)
	{
		int x, y, z;
		cin >> x >> y >> z;
		add(x, y, z);
	}
	if (!spfa(S))cout << "有负环" << endl;
	else cout << dis[T] << endl;
	return 0;
}
//tips:与Dijkstra的不同:1.可以判断有负环
//2.vis数组表示是否进队列3.Spfa容易被卡,Dijkstra的堆优化比Spfa快,
//图越复杂Spfa越被卡,Dijkstra更好用

多源最短路(floyd)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 100;
typedef struct
{
	char s[maxn];
	int vnum;
	int edgnum;
	int matrix[maxn][maxn];
}Graph;

int pre[maxn][maxn]; 
//记录介点,p[1][3]=2说明从顶点1到顶点3要经过顶点2
int dis[maxn][maxn]; 
//记录顶点间的最短路径值

void Floyd(Graph G, int p[maxn][maxn], int d[maxn][maxn])
{
	//初始化floyd的两个矩阵
	for (int i = 0; i < G.vnum; i++)
	{
		for (int j = 0; j < G.vnum; j++)
		{
			dis[i][j] = G.matrix[i][j];
			pre[i][j] = j;
		}
	}

	//核心 k为介点,i为起点,j为终点
	for (int k = 0; k < G.vnum; k++)
	{
		for (int i = 0; i < G.vnum; i++)
		{
			for (int j = 0; j < G.vnum; j++)
			{
				if (dis[i][j] > dis[i][k] + dis[k][j])
				{
					dis[i][j] = dis[i][k] + dis[k][j];
					pre[i][j] = k;
				}
			}
		}
	}
}
int main()
{
	ios::sync_with_stdio(false);
	int v, w;
	Graph G;
	cin >> G.vnum;
	for (int i = 0; i < G.vnum; i++)
	{
		for (int j = 0; j < G.vnum; j++)
		{
			cin >> G.matrix[i][j];
		}
	}
	Floyd(G, pre, dis);
	//floyd的D矩阵
	for (int i = 0; i < G.vnum; i++)
	{
		for (int j = 0; j < G.vnum; j++)
		{
			cout << dis[i][j] << " ";
		}cout << endl;
	}
	cout << endl;

	//floyd的P矩阵
	for (int i = 0; i < G.vnum; i++)
	{
		for (int j = 0; j < G.vnum; j++)
		{
			cout << pre[i][j] << " ";
		}cout << endl;
	}
	cout << endl;

	//输入起点与终点
	int start, endaim;
	cin >> start >> endaim;
	//输出最短路径值
	cout << dis[start][endaim] << endl;
	//输出路径点
	cout << start;
	int k = pre[start][endaim];
	while (k != endaim)
	{
		cout << "--->" << k;
		k = pre[k][endaim];
	}
	cout << "--->" << endaim << endl;
	return 0;
}
/*
7
0 12 1000 1000 1000 16 14
12 0 10 1000 1000 7 1000
1000 10 0 3 5 6 1000
1000 1000 3 0 4 1000 1000
1000 1000 5 4 0 2 8
16 7 6 1000 2 0 9
14 1000 1000 1000 8 9 0
0 3


0 12 22 22 18 16 14
12 0 10 13 9 7 16
22 10 0 3 5 6 13
22 13 3 0 4 6 12
18 9 5 4 0 2 8
16 7 6 6 2 0 9
14 16 13 12 8 9 0

0 1 1 5 5 5 6
0 1 2 2 5 5 5
1 1 2 3 4 5 4
5 2 2 3 4 4 4
5 5 2 3 4 5 6
0 1 2 4 4 5 6
0 5 4 4 4 5 6


22
0--->5--->4--->3
*/

分层图最短路(dj)

问题:分层图最短路是指在可以进行分层图的图上解决最短路问题。
分层图:可以理解为有多个平行的图。
一般模型是:在一个正常的图上可以进行 k 次决策,对于每次决策,
不影响图的结构,只影响目前的状态或代价。
一般将决策前的状态和决策后的状态之间连接一条权值为决策代价的边,
表示付出该代价后就可以转换状态了。
一般有两种方法解决分层图最短路问题:
1.建图时直接建成k + 1层。
2.多开一维记录机会信息。当然具体选择哪一种方法,看数据范围吧 。
法一:开4nk的数据


#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define pi pair<int,int>
#define inff 0x3f3f3f3f
const int maxn = 5e5 + 20;
struct
{
	int to, next, w;
}edg[maxn];
int head[maxn];
int dis[maxn];
int vis[maxn];
int cnt;
int n, m, s, t, k;
void init()
{
	memset(head, -1, sizeof(head));
	memset(dis, 0x3f, sizeof(dis));
	memset(vis, 0, sizeof(vis));
	cnt = 0;
}
void add(int u, int v, int w)
{
	edg[++cnt].to = v;
	edg[cnt].w = w;
	edg[cnt].next = head[u];
	head[u] = cnt;
}
void dijkstra()
{
	priority_queue<pi, vector<pi>, greater<pi> >q;
	dis[s] = 0;
	q.push({ dis[s],s });
	while (!q.empty())
	{
		int now = q.top().second;
		q.pop();
		if (vis[now])continue;
		vis[now] = 1;
		for (int i = head[now]; i; i = edg[i].next)
		{
			int v = edg[i].to;
			if (!vis[v] && dis[v] > dis[now] + edg[i].w)
			{
				dis[v] = dis[now] + edg[i].w;
				q.push({ dis[v],v });
			}
		}
	}
}
int main()
{
	ios::sync_with_stdio(false);
	while (cin >> n >> m >> s >> t >> k)
	{
		init();
		while (m--)
		{
			int u, v, w;
			cin >> u >> v >> w;
			for (int i = 0; i <= k; i++)
			{
				add(u + i * n, v + i * n, w);
				add(v + i * n, u + i * n, w);
				if (i != k)
				{
					add(u + i * n, v + (i + 1) * n, 0);
					add(v + i * n, u + (i + 1) * n, 0);
				}
			}
		}
		dijkstra();
		int ans = inff;
		for (int i = 0; i <= k; i++)ans = min(ans, dis[t + i * n]);
		cout << ans << endl;
	}
	return 0;
}




法二
我们把dis数组和vis数组多开一维记录k次机会的信息。
dis[i][j] 代表到达 i 用了 j 次免费机会的最小花费.
vis[i][j] 代表到达 i 用了 j 次免费机会的情况是否出现过.
更新的时候先更新同层之间(即花费免费机会相同)的最短路,
然后更新从该层到下一层(即再花费一次免费机会)的最短路。
不使用机会 dis[v][c] = min(min,dis[now][c] + edge[i].w);
使用机会 dis[v][c + 1] = min(dis[v][c + 1],dis[now][c];


#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define pi pair<int,int>
#define inff 0x3f3f3f3f
const int maxn = 1e6 + 20;
struct node
{
	int to, next, w;
}edg[maxn];
int head[maxn];
int dis[maxn][20];
int vis[maxn][20];
int cnt;
int n, m, s, t, k;
void init()
{
	memset(head, -1, sizeof(head));
	memset(dis, 0x3f, sizeof(dis));
	memset(vis, 0, sizeof(vis));
	cnt = 0;
}
void add(int u, int v, int w)
{
	edg[++cnt].to = v;
	edg[cnt].w = w;
	edg[cnt].next = head[u];
	head[u] = cnt++;
}
void dijkstra()
{
	priority_queue<pi, vector<pi>, greater<pi> >q;
	dis[s][0] = 0;
	q.push({ 0,s });
	while (!q.empty())
	{
		int now = q.top().second;
		q.pop();
		int ceng = now / n;
		now %= n;
		if (vis[now][ceng])continue;
		vis[now][ceng] = 1;
		for (int i = head[now]; i != -1; i = edg[i].next)
		{
			int v = edg[i].to;
			if (!vis[v][ceng] && dis[v][ceng] > dis[now][ceng] + edg[i].w)
			{
				dis[v][ceng] = dis[now][ceng] + edg[i].w;
				q.push({ dis[v][ceng],v + ceng * n });
			}
		}
		if (ceng < k)
		{
			for (int i = head[now]; i != -1; i = edg[i].next)
			{
				int v = edg[i].to;
				if (!vis[v][ceng + 1] && dis[v][ceng + 1] > dis[now][ceng])
				{
					dis[v][ceng + 1] = dis[now][ceng];
					q.push({ dis[v][ceng + 1],v + (ceng + 1) * n });
				}
			}
		}
	}
}
int main()
{
	ios::sync_with_stdio(false);
	while (cin >> n >> m >> s >> t >> k)
	{
		init();
		while (m--)
		{
			int u, v, w;
			cin >> u >> v >> w;
			add(u, v, w);
			add(v, u, w);
		}
		dijkstra();
		int ans = inff;
		for (int i = 0; i <= k; i++)ans = min(ans, dis[t][i]);
		cout << ans << endl;
	}
	return 0;
}

求数的直径(dfs)

//题意: 有一棵树,n − 1 n - 1n−1条边中,两两之间都有一个边权,
//可以从任意一个点出发,最多经过每一条边k次,只有第一次经过的时候
//才会算上边权的贡献,问最大的贡献是多少?
//首先k>=2时,可以将树遍历一遍,因此最大贡献为所有边权和
//k==1时,求树的直径最大
//思路:先求1到p点距离最大,再求p到其他点的最大距离
//参考代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2e5 + 100;
int num;
int h[maxn];
ll dis[maxn];
bool vis[maxn];
struct node
{
	int to, nt, w;
}e[maxn * 2];
void init()
{
	num = 0;
	memset(h, 0, sizeof(h));
}
void add(int u, int v, int w)
{
	e[++num].to = v;
	e[num].nt = h[u];
	e[num].w = w;
	h[u] = num;
}
void dfs(int s)
{
	vis[s] = 1;
	for (int i = h[s]; i; i = e[i].nt)
	{
		int v = e[i].to;
		if (vis[v])continue;
		dis[v] = dis[s] + e[i].w;
		dfs(v);
	}
}
int n, k;
ll sum;
int main()
{
	ios::sync_with_stdio(false);
	cin >> n >> k;
	init();
	for (int i = 1; i <= n - 1; i++)
	{
		int u, v, w;
		cin >> u >> v >> w;
		add(u, v, w);
		add(v, u, w);
		sum += w;
	}
	if (k >= 2)
	{
		cout << sum << endl;
		return 0;
	}
	memset(vis, 0, sizeof(vis));
	memset(dis, 0, sizeof(dis));
	dfs(1);
	ll mx = 0ll, p;
	for (int i = 1; i <= n; i++)
	{
		if (dis[i] > mx)
		{
			mx = dis[i];
			p = i;
		}
	}
	memset(vis, 0, sizeof(vis));
	memset(dis, 0, sizeof(dis));
	dfs(p);
	//mx=0; mx可以不用再初始为0,想一想,为什么
	for (int i = 1; i <= n; i++)mx = max(mx, dis[i]);
	cout << mx << endl;
	return 0;
}

求连通分支数(并查集)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int t;
typedef struct
{
	int u, v;
	ll w;
}S;
S s[500005];
int n, m, k;
bool cmp(S p, S q)
{
	return p.w < q.w;
}
int pre[1000005];
int find(int x)
{
	if (pre[x] == x)return x;
	else return pre[x] = find(pre[x]);
}

int main()
{
	ios::sync_with_stdio(false);
	cin >> t;
	while (t--)
	{
		cin >> n >> m >> k;
		for (int i = 1; i <= n; i++)pre[i] = i;
		for (int i = 1; i <= m; i++)cin >> s[i].u >> s[i].v >> s[i].w;
		sort(s + 1, s + 1 + m, cmp);
		int cnt = n; //连通分支数
		ll ans;
		for (int i = 1; i <= m; i++)
		{
			if (s[i].w != s[i - 1].w)
			{
				if (cnt == k)
				{
					ans = s[i - 1].w;
					break;
				}
			}
			if (find(s[i].u) == find(s[i].v))continue; 
			//同一连通分支,连通分支数不变
			pre[find(s[i].u)] = find(s[i].v);
			cnt--;
			//不同连通分支则合并,连通分支数-1
		}
		if (cnt == k)cout << ans << endl;
		else cout << -1 << endl;
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值