》》》算法竞赛
/**
* @file
* @author jUicE_g2R(qq:3406291309)————彬(bin-必应)
* 一个某双流一大学通信与信息专业大二在读
*
* @brief 一直在算法竞赛学习的路上
*
* @copyright 2023.8
* @COPYRIGHT 原创技术笔记:转载需获得博主本人同意,且需标明转载源
*
* @language C++
* @Version 1.0还在学习中
*/
UpData Log 2023.8.31 更新进行中
Statement0 一起进步
Statement1 素质三连,先赞再看
贪心算法的核心:是只管当前最好(局部最优)
BFS+优先队列
和Dijkstra算法
在实现时都体现了这个“贪于短期”的思想
规定从 A 点开始,第一层:将 A 的两个邻居 B、C先后进入优先队列中,基于贪心思想,6(A,B) > 3(A,C)
,弹出 C点 ;第二层:将 C 的另外两个邻居 D、E也放入优先队列中,比较后 B 更优,依次同理。
但是仍有小的区别,在
BFS+优先队列
中,队列存的是从起点到终点的距离
(1)基本数据类型的优先级设置
默认情况下,优先级是数值越大优先级越高
priority_queue<int> Q; //优先级与数值成正比(即默认情况)
priority_queue<int,vector<int>,less<int>> Q; //less:成正比
其中 vector
(也就是第二个参数)填写的是来承载底层数据结构堆(heap)的容器,如果第一个参数是double型或char型,则此处只需要填写vector-或vector。
而第三个参数less-则是对第一个参数的比较类, less表示数字大的优先级越大,而greater表示数字小的优先级越大。
因此,如果想让优先队列总是把最小的元素放在队首,只需进行如下定义:
priority_queue<char,vector,greater<char>> Q; //greater:成反比
注:这种是坚决不能用在存储结构体的队列的!!!,亲测报错。
(2)结构体的优先级设置:重载号 ‘<’ 的使用
拓:关键字 友元friend
打个比方,朋友之间(可以是一个朋友群)是值得信任的,所以可以对他们公开一些自己的隐私(私有成员,可以先简单的认为是私有参数)
优点:解决了类与类间的数据访问(无需再通过类提供的接口间接进行)
struct node{
int id; //结点号
LL n_dis; //该节点到初始点
friend bool operator < (node n1,node n2){
return n1.n_dis < n2.n_dis;} //成正比
};
若以距离越小(路径最优)的话,需略加修改如下:
return n1.n_dis > n2.n_dis;//成反比
思索过后,会容易地发现,此处重载
(降序排列)与排序函数sort中的cmp函数
(升序排列)有些相似,它们接收的参数都是两个,函数内部都是return
的true或者false
,事实上,这两者的作用确实是类似的只不过效果看上去似乎是“相反”的。
另一种写法是:
bool operator < (const node& bigger) const {
return this->n_dis > bigger.n_dis;} //成正比
(3)特殊方法
struct node{
int id; //结点号
LL n_dis; //该节点到初始点
};
struct inverse{ //必须要用struct封装该函数 inverse(使反比)
bool operator () (node n1,node n2){
return n1.n_dis > n2.n_dis;} //成反比
}
调用的时候只需要将第一种方法的第三个参数(less / greater)换成 函数名 inverse
priority_queue<node,vector<node>,inverse<node>> Q; //inverse函数
实际上就是抛弃原来 priority_queue
内置的 less() / great()
函数不用而自行定义函数实现用户特定功能。
最短路径
要求
给定一个图,求1到其他所有点最短路径
Input
第一行输入n(点的个数),m(边的个数)
接下来的每行输入u(边),v(边),w(边间长)
最后一行输入要到达的元素
4 6 1 2 2 2 3 2 2 4 1 1 3 5 3 4 3 1 4 4
Output
无法到达输出-1,可到达输出到其他每个元素最短路径长(BFS+优先队列要求),路径详情(Dijkstra+优先队列)
1到1的最短距离为:0 1到2的最短距离为:2 1到3的最短距离为:4 1到4的最短距离为:3 //还有一空行
首先了解一下解该题的一个数据存储芝士:
vector
>
vector
的这种用法有点类似于 map
。
map | vector |
---|---|
会对插入的元素按键自动排序 | 不会自动排序 |
不允许重复 | 允许重复 |
插入元素时
vector<pair<int,int>> Graph;//声明图
Graph.emplace_back(1,1);//存入
#include
using namespace std;
using LL=long long int;
using cla=pair<int,LL>;
const int MAX=3e5+2;
const LL INF=0x3f; //使得 条件(INF > INF+x)不成立
vector<cla> G[MAX]; //G图G[][0]:目标点,G[][1]:边权重
vector<int> Node(MAX); //反向存储,配合栈正序输出最短路径
priority_queue<cla,vector<cla>,greater<cla>> Q;
LL dis[MAX];
int pace=0;
void ShortestPath(void){
vector<bool> found(MAX,false);
Q.emplace(1,0); //起点1到自己的距离为0:to=1,dis=0
dis[1]=0;
//found[1]=true;
while(!Q.empty()){
cla cur_node=Q.top(); Q.pop();
int cur_id=cur_node.first;
if(found[cur_id])
continue; //若此点已确定最短距离则跳过此点并进行下一次循环
found[cur_id]=true; //本次循环将确定该点的最短距离
for(std::size_t i=0;i<G[cur_id].size();i++){//查找cur_node的所有邻居(G[cur_id].size()=拥有cur_id的子结点个数)
cla son_node=G[cur_id][i]; //son_node 是 存入当前元素结点的第i个邻居 的拥有cur_id编号集合的一个子结点
int near_id=son_node.first; //查出该邻居的元素值
int sonTOnear=son_node.second; //查出两端间边的权重
if(found[near_id])
continue; //丢弃已经在最短路径上的结点
if(dis[near_id] > dis[cur_id]+sonTOnear){//优化路径(贪心)
dis[near_id]=dis[cur_id]+sonTOnear; //更新
Q.emplace(near_id,dis[near_id]); //放入优先队列,看看是否有更优的解(贪心)
Node[near_id]=cur_id;
}
}
}
}
int main(void){
int point,edge; //端点数,边数
scanf("%d %d",&point,&edge);
memset(dis,INF,sizeof(dis)); //初始化距离为不可达,切勿赋初值为-1
while(edge--){ //存入两端点间(边)连接情况
int from,to; LL cost;
scanf("%d %d %lld", &from, &to, &cost);
G[from].emplace_back(to,cost); //(只需要)单向边
}
ShortestPath();
for(int i=1;i<=point;i++){
if(dis[i]>=INF) dis[i]=-1;
printf("1到%d的最短距离为:%lld\n",i,dis[i]);
}
return 0;
}
vector
中 emplace
与 emplace_back
的使用emplace
用于一般的容器(动态数组
),向尾端(无需拷贝的【注:这是比 push_back
更优的地方】)填充数据
emplace_back
用于已定义数组大小的容器,向指定下标
存储填充数据
for(std::size_t i=0;i
std::size_t
类型名是专门对 .size()
得到返回值的参数的类型定义,否则会遇到warning :comparison of integers of different signs(不同符号整数的比较)
点击参考blog 中对这种warning的介绍
这种情况的发生原因是:
G[cur_id].size()
是无符号整型
所以还有诸如的解决方法:
for (unsigned int i = 0; i<G[cur_id].size(); ++i)
for (vector<int>::size_type i = 0; i<G[cur_id].size(); ++i)
struct edge{ //用于存储用户输入的信息
int from,to; //当前起点,当前终点
LL cost; //边上的权值
edge(int f,int t,LL c){from=f,to=t,cost=c;}//接收参数填充函数
} e[MAX]; */
struct node{ //存储最短路径
int id; //结点号
LL n_dis; //该节点到初始点
node(int i,int d){id=i,n_dis=d;}
void PrintPath(void){
cout<< id << ":" << n_dis << " -> ";}//端点打印函数
friend bool operator < (const Node& bigger) const{
return n_dis > bigger.n_dis;}
} n[MAX];
priority_queue<node> Q;
vector
的用法1、对容器提前赋予数组大小最好是以这种格式定义 vector
,这样能够直接对所在下标进行赋值: Q[index]=val;
2、备份容器操作:
vector<int> Q(MAX,0)//主容器
vector<int> save(Q)//备份容器
3、存取一般数组的子序列
int arr[]={0,1,2,3,4};
vector<int> SubArr(arr,arr+2);//arr的子序列,i范围[0,1]!!!
补充说明 5 :
我们在上述代码里只得到了 BFS+优先队列
需要的 起点1 其他点的最短距离,还有一个任务就是得出实际的最短路径
在 if(dis[near_id] > dis[cur_id] + sonTOnear)
我已提前写下 Node[near_id]=cur_id;
用来存储最短路径的结点
只需要添加一个 PrintPath
函数来进行 递归
(实质是栈
) 实现打印最短路径上结点的功能
void PrintPath(int target){
if(1==target){
cout<<1; return;
}
PrintPath(Node[target]);
cout<<" -> "<<target;
return;
}
修bug时发现的非常容易犯的逻辑错误
原来代码有一行注释掉了 //found[1]=true;
(这处问题找了很久)这个 bug 就是只输出 0 ,也就只输出了起点的距离,让人误以为是循环while里卡死了。
其实就是这一句看似严谨的初始化使得循环中的一句 if(found[cur_id]) continue;
成立,就会直接进入下一次循环,而下次循环时,Q中已经没有结点了,直接跳出循环。
不需要这一句初始化也不会影响,因为 if(found[cur_id]) continue;
不成立后立马就有 found[cur_id]=true;
将其标记。