【算法】图论专题C++

date: 2022-02-22

一、知识点

(1)Dijkstra算法,最短路径算法。

从一个顶点到其余各顶点的最短路径算法,解决的是有正权图中最短路径问题。迪杰斯特拉算法主要特点是从起始点开始,采用贪心算法的策略,每次遍历到始点距离最近且未访问过的顶点的邻接结点,直到扩展到终点为止。

typedef pair<int, int> PII;
vector<PII> e[N];

int dist[N];//存储各点到起点的最短距离
bool st[N];
int n, m;

int dijkstra()  // 求1号点到n号点的最短路距离,如果从1号点无法走到n号点则返回-1
{
    memset(dist, 0x3f, sizeof dist);  //全部初始化为0x3f3f3f3f,表无穷
    dist[1] = 0;          //初始化:1号点到1号点的距离为0
    priority_queue<PII, vector<PII>, greater<PII>> heap;   //优先队列
    heap.push({0, 1});                 //将起点放入优先队列

    while (heap.size())                //只要队列不为空,就循环下去,和bfs原理一致
    {
        auto t = heap.top();           //获取队头元素
        heap.pop();                    //弹出队头元素

        int ver = t.second, distance = t.first;   //以距离更短优先存储的优先队列

        if (st[ver]) continue;                    //如果该点已经有最短距离
        st[ver] = true;                           //标记该点

        for (int i = 0; i < e[ver].size(); i ++)  
        {
            int j = e[ver][i].second;
            int w = e[ver][i].first;
            if (dist[j] > dist[ver] + w)
            {
                dist[j] = dist[ver] + w;
                heap.push({dist[j], j});
            }
        }
    }

    if (dist[n] == 0x3f3f3f3f) return -1;
    return dist[n];
}

(2)最小生成树算法。

  • 生成树:一个连通图的生成树是指一个连通子图,它含有图中全部n个顶点,但只有足以构成一棵树的n-1条边。一颗有n个顶点的生成树有且仅有n-1条边,如果生成树中再添加一条边,则必定成环。
  • 最小生成树:在连通网的所有生成树中,所有边的代价和最小的生成树,称为最小生成树。
  • Prim算法:“加点法”,每次迭代选择代价最小的边对应的点,加入到最小生成树中。算法从某一个顶点 s 开始,集合逐渐增大至覆盖整个连通网的所有顶点。
  • Kruskal算法:“加边法”,初始最小生成树边数为0,每迭代一次就选择一条满足条件(即能够使两个集合连通的边)的最小代价边,加入到最小生成树的边集合里。
//prim算法代码模板,和dijkstra算法十分相像,只是dist的定义不同。并且总最短路距离需要一个变量sum来存储。
typedef pair<int, int> PII;

int dist[N]; //存储的是各点到集合的最短距离 
vector<PII> e[N];
bool st[N];

int prim(){
	long long sum = 0;
	memset(dist, 0x3f, sizeof dist);
	priority_queue<PII, vector<PII>, greater<PII>> q;
	dist[1] = 0;
	q.push({0, 1});
	while(q.size()){
		auto t = q.top();
		q.pop();
		int d = t.first;
		int ver = t.second;
		if(st[ver]) continue;
		st[ver] = true; 
		sum += d;
		for(int i = 0; i < (int)e[ver].size(); i++){
			int w = e[ver][i].first;
			int j = e[ver][i].second;
			if(w >= dist[j]) continue;
			dist[j] = w;
			q.push({w, j});
		}
	}
	for(int i = 1; i <= n; i ++){
		if(dist[i] == 0x3f3f3f3f) return -1;
	}
	return sum;
}

(3)stl 中的使用。

  • vector<类型>标识符:定义。
  • void push_back(const T& x):向量尾部增加一个元素X。
  • int size() const:返回向量中元素的个数。
  • void pop_back():删除向量中最后一个元素。
  • void clear():清空向量中所有元素。
  • front():返回第一个元素。
  • back():返回最后一个元素。
  • begin():返回第一个迭代器。
  • end():返回最后一个迭代器的下一个位置。
  • 可直接使用数组访问也可使用迭代器访问

(4)stl 中,priority_queue优先队列的使用。优先队列具有队列的所有特性,包括基本操作,只是在这基础上添加了内部的一个排序,它本质是一个堆实现的。

  • priority_queue<类型, 容器类型<类型>, 比较的方式<类型>>:定义。容器类型默认使用vector,比较方式默认是less。
  • top():获取队头元素。
  • 其他操作与queue操作类似。

二、真题运用

【蓝桥杯2013真题J】大臣的旅费

很久以前,T王国空前繁荣。

为了更好地管理国家,王国修建了大量的快速路,用于连接首都和王国内的各大城市。

为节省经费,T国的大臣们经过思考,制定了一套优秀的修建方案,使得任何一个大城市都能从首都直接或者通过其他大城市间接到达。

同时,如果不重复经过大城市,从首都到达每个大城市的方案都是唯一的。

J是T国重要大臣,他巡查于各大城市之间,体察民情。

所以,从一个城市马不停蹄地到另一个城市成了J最常做的事情。

他有一个钱袋,用于存放往来城市间的路费。

聪明的J发现,如果不在某个城市停下来修整,在连续行进过程中,他所花的路费与他已走过的距离有关,在走第x千米到第x+1千米这一千米中(x是整数),他花费的路费是x+10这么多。也就是说走1千米花费11,走2千米要花费23。

J大臣想知道:他从某一个城市出发,中间不休息,到达另一个城市,所有可能花费的路费中最多是多少呢?

输入格式

输入的第一行包含一个整数 n,表示包括首都在内的T王国的城市数。

城市从 1 开始依次编号,1 号城市为首都。

接下来 n−1 行,描述T国的高速路(T国的高速路一定是 n−1 条)。

每行三个整数 Pi,Qi,Di,表示城市 Pi 和城市 Qi 之间有一条双向高速路,长度为 Di 千米。

输出格式

输出一个整数,表示大臣J最多花费的路费是多少。

数据范围

1≤n≤105,
1≤Pi,Qi≤n,
1≤Di≤1000

输入样例:

5 
1  2  2 
1  3  1 
2  4  5 
2  5  4 

输出样例:

135

解题思路

本题的做法巧妙地运用了"树的直径",一张无环无向连通图,即是一棵树,无向边的数量恰好等于顶点数量 -1。

顶点和顶点之间有且仅有一条通路,计算树的直径,即最长的一条路径

  1. 先存储边的相关信息。
  2. 利用 dfs 遍历找寻随机一点 a 到其他点之间的距离后,循环遍历各距离,找出最长的距离所对应的点 b 。
  3. 再 dfs 一次,求点 b 到其他点之间的距离,再次循环遍历出最大距离 s,s 即为树的直径。

完整代码

#include<iostream>
#include<cstring>
#include<cstdio>
#include<vector>
#define N 100005

using namespace std;
int dist[N];

struct EDGE{
    int v;
    int e;
};

vector<EDGE> h[N];
void dfs(int x, int father, int dis){
    dist[x] = dis;
    for(int i = 0; i < h[x].size(); i++){
        if(h[x][i].v != father){ //防止往回走,造成错误以及死循环
            dfs(h[x][i].v, x, dis + h[x][i].e);
        }
    }
}

int main(){
    int n, l = 0;
    scanf("%d", &n);
    for(int i = 0; i < n-1; i++){
        int p, q, x;
        scanf("%d%d%d",&p, &q, &x);
        h[p].push_back({q, x});
        h[q].push_back({p, x});
    }

    dfs(1, -1, 0);      //从结点1开始;上一个结点不存在,因此使用并不存在的结点编号-1;起始结点到起始结点之间的距离为0 
    int u = 1; 
    for(int i = 1; i <=  n; i++){
        if(dist[i] > l){
            l = dist[i];
            u = i;              
        }
    }

    dfs(u, -1, 0);

    for(int i = 1; i <=  n; i++){
        if(dist[i] > l){
            l = dist[i];                
        }
    }
    long long s = 0;
    s = (1ll+l)*l/2 + 10*l;     //1ll为类型为long long的 1 
    printf("%lld", s);
    return 0;
}

【蓝桥杯2021真题E】路径

在这里插入图片描述

解题思路

gcd + lcm + dijkstra

#include<iostream>
#include<cstring> 
#include<cstdio>
#include<queue>
#include<vector>
#define N 2500

using namespace std;

int dist[N];
typedef pair<int, int> PII;
vector<PII> e[N];
bool st[N];

int gcd(int a, int b){
	return b ? gcd(b, a % b) : a;
}

int lcm(int a, int b){
	return a * b / gcd(a, b);
}

int dijkstra(){
	memset(dist, 0x3f, sizeof dist);
	priority_queue<PII, vector<PII>, greater<PII>> heap;
	heap.push({0, 1});
	dist[1] = 0;
	while(heap.size()){
		auto t = heap.top();
		heap.pop();
		int ver = t.second;
		if(st[ver]) continue;
		else st[ver] = true;

		for(int i = 0; i < e[ver].size(); i++){
			int j = e[ver][i].second;
			int w = e[ver][i].first;
			if(dist[j] > dist[ver] + w){
				dist[j] = dist[ver] + w;
				heap.push({dist[j], j});
			}
		}                                         
	}
	if(dist[2021] == 0x3f3f3f3f) return -1;
	else return dist[2021]; 
}

int main(){
	for(int i = 1; i <= 2021; i++){
		for(int j = i + 1; j <= i + 21; j++){
			int l = lcm(i, j);
			e[i].push_back({l, j});
			e[j].push_back({l, i});
		}
	}
	printf("%d", dijkstra());
	return 0;
}

答案:10266837。

【蓝桥杯2021第二场真题E】城邦

小蓝国是一个水上王国,有 2021 个城邦,依次编号 1 到 2021,在任意两个城邦之间,都有一座桥直接连接。
为了庆祝小蓝国的传统节日,小蓝国政府准备将一部分桥装饰起来。
对于编号为 a 和 b 的两个城邦,它们之间的桥如果要装饰起来,需要的费用如下计算:
找到 a 和 b 在十进制下所有不同的数位,将数位上的数字求和。
例如,编号为 2021 和 922 两个城邦之间,千位、百位和个位都不同,将这些数位上的数字加起来是 (2+0+1) + (0+9+2) = 14。

注意 922 没有千位,千位看成 0。
为了节约开支,政府准备只装饰 2020 座桥,并且要保证从任意一个城邦到任意另一个城邦之间可以完全只通过装饰的桥到达。

请问,小蓝国政府至少要花多少费用才能完成装饰。提示:建议使用计算机编程解决问题。

解题思路

字符串处理 + prim,其中使用了 to_string,蓝桥杯的大题里可能是不支持的,但是这是填空题谁管呢~

#include<iostream>
#include<algorithm>
#include<cstring>
#include<string>
#include<cstdio> 
#include<vector>
#include<queue>
#define N 2050

using namespace std;

typedef pair<int, int> PII;

int dist[N]; //prim存储的是各点到集合的距离 
vector<PII> e[N];
bool st[N];
string s[N];

int prim(){
	long long sum = 0;
	memset(dist, 0x3f, sizeof dist);
	priority_queue<PII, vector<PII>, greater<PII>> q;
	dist[1] = 0;
	q.push({0, 1});
	while(q.size()){
		auto t = q.top();
		q.pop();
		int d = t.first;
		int ver = t.second;
		if(st[ver]) continue;
		st[ver] = true; 
		sum += d;
		for(int i = 0; i < (int)e[ver].size(); i++){
			int w = e[ver][i].first;
			int j = e[ver][i].second;
			if(w >= dist[j]) continue;
			dist[j] = w;
			q.push({w, j});
		}
	}
	for(int i = 1; i <= 2021; i ++){
		if(dist[i] == 0x3f3f3f3f) return -1;
	}
	return sum;
}

int main(){
	for(int i = 1; i <= 2021; i++){
		s[i] = to_string(i);
		if(i < 10) s[i] = "000" + s[i];
		else if(i < 100) s[i] = "00" + s[i];
		else if(i < 1000) s[i] = "0" + s[i];
	}

	for(int i = 1; i <= 2021; i++){
		for(int j = i + 1; j <= 2021; j++){
			int len = 0;
			for(int k = 0; k < 4; k++){
				if(s[i][k] == s[j][k]) continue;
				len += s[i][k] + s[j][k] - 48 - 48;
			}
			e[i].push_back({len, j});
			e[j].push_back({len, i});
		}
	}
	cout << prim();
	return 0;
}
  • 18
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
ACM 算法模板集 Contents 一. 常用函数与STL 二. 重要公式与定理 1. Fibonacci Number 2. Lucas Number 3. Catalan Number 4. Stirling Number(Second Kind) 5. Bell Number 6. Stirling's Approximation 7. Sum of Reciprocal Approximation 8. Young Tableau 9. 整数划分 10. 错排公式 11. 三角形内切圆半径公式 12. 三角形外接圆半径公式 13. 圆內接四边形面积公式 14. 基础数论公式 三. 大数模板,字符读入 四. 数论算法 1. Greatest Common Divisor最大公约数 2. Prime素数判断 3. Sieve Prime素数筛法 4. Module Inverse模逆元 5. Extended Euclid扩展欧几里德算法 6. Modular Linear Equation模线性方程(同余方程) 7. Chinese Remainder Theorem中国余数定理(互素于非互素) 8. Euler Function欧拉函数 9. Farey总数 9. Farey序列构造 10. Miller_Rabbin素数测试,Pollard_rho因式分解 五. 图论算法 1. 最小生成树(Kruscal算法) 2. 最小生成树(Prim算法) 3. 单源最短路径(Bellman-ford算法) 4. 单源最短路径(Dijkstra算法) 5. 全源最短路径(Folyd算法) 6. 拓扑排序 7. 网络预流和最大流 8. 网络最小费用最大流 9. 网络最大流(高度标号预流推进) 10. 最大团 11. 二分图最大匹配(匈牙利算法) 12. 带权二分图最优匹配(KM算法) 13. 强连通分量(Kosaraju算法) 14. 强连通分量(Gabow算法) 15. 无向图割边割点和双连通分量 16. 最小树形图O(N^3) 17. 最小树形图O(VE) 六. 几何算法 1. 几何模板 2. 球面上两点最短距离 3. 三点求圆心坐标 4. 三角形几个重要的点 七. 专题讨论 1. 树状数组 2. 字典树 3. 后缀树 4. 线段树 5. 并查集 6. 二叉堆 7. 逆序数(归并排序) 8. 树状DP 9. 欧拉路 10. 八数码 11. 高斯消元法 12. 字符串匹配(KMP算法) 13. 全排列,全组合 14. 二维线段树 15. 稳定婚姻匹配 16. 后缀数组 17. 左偏树 18. 标准RMQ-ST 19. 度限制最小生成树 20. 最优比率生成树(0/1分数规划) 21. 最小花费置换 22. 区间K大数 23. LCA - RMQ-ST 24. LCA – Tarjan 25. 指数型母函数 26. 指数型母函数(大数据) 27. 单词前缀树(字典树+KMP) 28. FFT(大数乘法) 29. 二分图网络最大流最小割 30. 混合图欧拉回路 31. 无源汇上下界网络流 32. 二分图最小点权覆盖 33. 带约束的轨道计数(Burnside引理) 34. 三分法求函数波峰 35. 单词计数,矩阵乘法 36. 字符串和数值hash 37. 滚动队列,前向星表示法 38. 最小点基,最小权点基

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值