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。
顶点和顶点之间有且仅有一条通路,计算树的直径,即最长的一条路径:
- 先存储边的相关信息。
- 利用 dfs 遍历找寻随机一点 a 到其他点之间的距离后,循环遍历各距离,找出最长的距离所对应的点 b 。
- 再 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;
}