题解 P1828 【香甜的黄油 Sweet Butter】- 优先队列

· · 题解

我觉得这道题目里面的dijkstra堆优化不适合竞赛党,所以我就重新发了一篇语法比较标准的,希望对大家有帮助。但是,该题解因为从零开始介绍了多种STL/C++特殊语法,所以看下来还是需要勇气的

=========↓忙人请跳过分割线内的内容↓=========

这道题是一个比较坑的题目。。。我交了5次才过。

瞎扯一下我的辛酸史:

  1. floyd写错--10分
  2. floyd写对了但是超时--80分
  3. 迪杰斯特拉堆优化(优先队列优化)写错--10分
  4. 迪杰斯特拉堆优化(优先队列优化)写对但是用的邻接矩阵TLE--70分
  5. 取得真经(迪杰斯特拉堆优化+邻接表)--AC

=========↑忙人请跳过分割线内的内容↑=========

简述dijkstra堆(优先队列)优化

在裸的dijkstra中,我们发现,我们每次都要O(n)的复杂度来寻找和当前点距离最近的点。于是,我们很自然的想到可以用数据结构来优化复杂度。这次我们选到的是STL优先队列。(接下来会介绍在这道题里面优先队列会使用的基本操作)

STL的内部结构是二叉堆(不懂二叉堆的同学戳这里和这里)。它插入元素、取出元素的时间复杂度均为O(logN)。有同学要问了:取出元素不是O(1)吗?因为取出元素后还要维护最小(最大)堆的特性,所以其实是O(logN)

优先队列我们要用到的操作:

  1. 定义一个优先队列

    priority_queue <类型名> 队列名称;

    例:

    priority_queue <int> k;//k为自定义名称
  2. 插入一个元素(2~5操作不会举例)

    队列名称.push(元素名称);
  3. 删除队首元素

    队列名称.pop();
  4. 返回队列的元素个数

    队列名称.size();//返回值为整数,返回队列的元素个数
  5. 返回队列是否为空

    队列名称.empty();//返回值为布尔值,返回true表示队列为空,反之不为空

优先队列默认是把元素从大到小排序。

例如我一次性插入5个元素:

3 4 2 5 1

那么5次弹出时依次是:

5 4 3 2 1

而在图论题目中,我们常常要使用结构体,这时候系统会怎样排序呢?我们不清楚。这个时候,我们就需要重载运算符。

==========我是分割线============

不得不提的:重载运算符

重载运算符就是将一个运算符原来的运算方式改变。但是,重载运算符在一般使用中要注意三点:

第一、不改变返回值类型

C++的运算符%&&来举例,%的返回值是一个整数,代表两个整数相除的余数。&&是逻辑与,返回的是bool值,代表表达式中两个条件是否同时成立。

假设%返回了一个字符串,&&返回了一个整数...

这时候,编译就会出错。因为C++的运算符重载是不能改变返回值类型的。

第二、不改变运算目数(不改变运算参数数量)

什么是运算目数?在进行+运算时,我们要写成元素1 + 元素2的这种形式,也就是说,+要有两个参数元素1元素2才能进行运算,那么+的目数就是2。同样的,? :是三目运算符,!是单目运算符。在重载时,同样也不能改变运算目数(运算参数数量)。

第三、不能创造新的运算符

重载运算符过程中,只能从现有运算符选择一个进行重载,例如重载@就是错的,因为@不是一个运算符。

``` 重载前运算符的返回值类型 operator 要重载的运算符 (需要的参数列表) { //运算过程 return 重载前运算符的返回值类型的数据; } ``` 是不是看起来有些懵逼?别慌,让我慢慢解释。 重载运算符前的运算符的返回值类型:`&&`的返回值是布尔值,所以如果要重载`&&`,那么此处应该填`bool`。 要重载的运算符:可以是任何运算符。例如`+`、`[]`、`()`,`&&`等。 需要的参数列表:例如我要么我就要在这里写上`(int a,int b)`,当然,a,b都是可以自定义的数据名称。 运算过程:例如我来恶搞` + `,将其返回值恶搞为`a - b`,那么` a - b `就是运算过程。 结果:当然以`return`来返回啦,返回的结果要和该运算符的返回值类型一致。例如上面的`return a - b`就是一个很好的栗子。 第三、可以只重载该运算符在部分情况下使其只在部分情况下返回值为重载后的效果比如`+`运算符,我可以只在它的运算参数为结构体`node`时重载它,像下面这样: ``` .... struct node { int id,data; }; int operator + (node a,node b) { return a.data+b.data; } node a,b; int main() { cin>>a.data>>a.id>>b.data>>b.id; cout<<a+b; cout<<12+13; } ``` 输入: 10 2 13 1 输出: 23 25 为什么在两个整数相加的时候` + `的运算结果没有改变?因为,` + `仅仅在运算参数为两个` node `的时候才是我们重载之后的结果。**所以重载一个运算符,在参数不是我们重载是的参数类型的时候是不会返回重载结果的。** ==============分割线=============== 本题思路: 有了优先队列,又可以重载,我们就可以利用优先队列把距离起点最近的点放在队首,方便使用。这个时候像往常一样跑dj就可以AC了。记得一定要枚举每一个牧场! #### 不要用邻接矩阵! #### 不要用邻接矩阵! #### 不要用邻接矩阵! ====下面就贴代码了==== ``` # include <bits/stdc++.h>//万能头文件 using namespace std; const int kP=801;// int dis[kP],w[kP][kP],sum[kP],minx;//w[][]表示两点之间的距离,dis[]是从起点出发到各个点的最短路径,sum是每个牧场的奶牛数量,minx往下看 int ss[kP],lian[kP][kP];//ss[]记录每个牧场和其他牧场的道路数量,lian[i][j]表示第i个牧场的第j条路径所到达的牧场 bool c[kP];//是否已确定最短路 struct node { int id,W;//id是点的编号,W是当前(不一定最短)离起点的距离 }; int n,p,C;//如题目所述 bool operator <(node imh,node ghj)//开始重载 { return imh.W>ghj.W;//因为优先队列默认从大到小,所以我们需要反着 } priority_queue<node> k;//定义优先队列 inline int read()//快读模板,可以用cin和scanf代替 { int f=1,res; char c; while((c=getchar())<'0'||c>'9') if(c=='-')f=-1; res=c-48; while((c=getchar())>='0'&&c<='9') res=res*10+c-48; return res*f; } inline int min(const int &a,const int &b)//建议手写min函数优化常数 { return a<b?a:b; } void dijkstra(int);//最短路函数 int main() { n=read(); p=read(); C=read(); for(register int i=1;i<=p;i++) for(register int j=1;j<=p;j++) w[i][j]=255000;//最好用memset代替 int tmp; for(register int i=1;i<=n;i++) tmp=read(),sum[tmp]++;//输入每头奶牛所在的牧场 int u,v,ww; for(register int i=1;i<=C;i++) { u=read(); v=read(); ww=read(); w[u][v]=ww; w[v][u]=ww; ss[u]++; ss[v]++; lian[u][ss[u]]=v; lian[v][ss[v]]=u;//建图 } minx=1e8;//初始化为一个极大值 for(register int i=1;i<=p;i++) { memset(dis,0x3f3f3f3f,sizeof(dis));//清零 memset(c,false,sizeof(c));//清零 dijkstra(i);//求最短路 int ans=0; for(register int j=1;j<=p;j++) { ans+=dis[j]*sum[j]; }//累加距离 minx=min(minx,ans);//比较 } cout<<minx;//输出最小值 return 0; } void dijkstra(int s)//dj函数 { k.push((node){s,0});//将起点压入队列 dis[s]=0;//起点到起点距离为0 while(!k.empty())//队列不为空 { node toop=k.top();//取出队首元素 k.pop();//删掉 int i=toop.id;//这个点的编号 if(c[i])continue;//如果最短路已经确定,就结束本次循环 c[i]=true;//标记最短路已经确定 for(register int j=1;j<=ss[i];j++)//枚举每一个能到达的牧场 if(!c[lian[i][j]]&&dis[i]+w[i][lian[i][j]]<dis[lian[i][j]]) { dis[lian[i][j]]=dis[i]+w[i][lian[i][j]]; k.push((node){lian[i][j],dis[lian[i][j]]});//将能更新的点压入队列 }//dj板子不解释 } } ```