单源最短路的建图方式
热浪
题目链接:热浪
分析:比较裸的一道题,直接建图再套模板,我就直接用spfa水过了。
代码实现:
#include<iostream>
#include<queue>
#include<cstring>
using namespace std;
const int N=2510,M=12410;
int n,m,str,ed;
int dist[N];
int h[N],e[M],ne[M],idx=1,w[M];//链式前向星存储图
bool st[N];
queue<int> q;
void add(int a,int b,int c){
e[idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx++;
}
void spfa(){
memset(dist,0x3f,sizeof dist);
dist[str]=0;
q.push(str);
st[str]=1;
while(q.size()){
auto t=q.front();
q.pop();
st[t]=0;
for(int i=h[t];i;i=ne[i]){
int j=e[i];
if(dist[j]>dist[t]+w[i]){
dist[j]=dist[t]+w[i];
if(!st[j]) q.push(j),st[j]=1;//这里st[j]去掉也不错,但是效率会低一些
}
}
}
}
int main(){
cin>>n>>m>>str>>ed;
for(int i=1;i<=m;i++){
int a,b,c;
cin>>a>>b>>c;
add(a,b,c);
add(b,a,c);
}
spfa();
cout<<dist[ed]<<endl;
return 0;
}
信使
题目链接:信使
分析:对于此题,我们发现,对于除1外的每个点,通信所需的最短时间就是1到此点的最短路距离,而所有点全部完成通信的时间就是所有点到1(除1外)的最短路中的最大值,因此我们只需求一下1到所有点(除1外)的最短路即可。
代码实现:
#include<iostream>
#include<cstring>
using namespace std;
const int INF=0x3f3f3f3f;
int n,m;
const int N=110;
int g[N][N];
int main(){
cin>>n>>m;
memset(g,0x3f,sizeof g);
//这里我们不用初始化g[i][i]为0,因此更新的时候不会用到它,而更新的时候不用这个状态也对
while(m--){
int a,b,c;
cin>>a>>b>>c;
g[a][b]=g[b][a]=min(g[a][b],c);//防止有多条边
}
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
g[i][j]=min(g[i][j],g[i][k]+g[k][j]);
int res=0;
for(int i=2;i<=n;i++)//注意从2开始,如果从1开始,因为我们未把g[1][1]初始化为0,会导致错误
if(g[1][i]==INF) {
res=-1;
break;
}
else res=max(res,g[1][i]);
cout<<res;
return 0;
}
香甜的黄油
题目链接:香甜的黄油
分析:对于这道题,我们可以枚举每个牧场放糖,然后求出这个牧场到其它所有牧场的最短路,然后枚举每头牛所在牧场,把这些距离都求出来并取最小的一个。如果直接上dijkstra的话可能会超时吧,这里我们用spfa,这题出题人并没有卡spfa。
代码实现:
#include<iostream>
#include<queue>
#include<cstring>
using namespace std;
const int INF=0x3f3f3f3f;
int s,n,m,tmp;
const int N=810,M=2910;
int res=INF;
int id[N],e[M],ne[M],idx=1,h[N],w[M];
bool st[N];
int dist[N];
queue<int> q;
void add(int a,int b,int c){
e[idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx++;
}
int spfa(int str){
memset(dist,0x3f,sizeof dist);
q.push(str);
st[str]=1;
dist[str]=0;
while(q.size()){
auto t=q.front();
q.pop();
st[t]=0;
for(int i=h[t];i;i=ne[i]){
int j=e[i];
if(dist[j]>dist[t]+w[i]){
dist[j]=dist[t]+w[i];
if(!st[j]) q.push(j),st[j]=1;
}
}
}
int ans=0;
for(int i=1;i<=s;i++)
if(dist[id[i]]==INF) return INF;//一旦有两个牧场不连通,直接返回INF
else ans+=dist[id[i]];
return ans;
}
int main(){
cin>>s>>n>>m;
for(int i=1;i<=s;i++) cin>>id[i];//记录每头牛所在的牧场
while(m--){
int a,b,c;
cin>>a>>b>>c;
add(a,b,c),add(b,a,c);
}
for(int i=1;i<=n;i++) res=min(res,spfa(i));
cout<<res<<endl;
return 0;
}
最小花费
题目链接:最小花费
分析:比较有意思的一道题,我们分别用spfa和dijkstra讲解一遍。
我们先考虑,假设A点的钱数为SA,经过每个点后剩余的钱为原来的Ci倍,它经过所有点到B后有一个等式SA*C1*C2*C3......=100
,我们要求SA的最小值,就要求那一堆C的乘积的最大值,又因为Ci大于0小于等于1,所以越乘越小,接下来我们讨论两种做法。
spfa:spfa是基于它的松弛操作,对于一个起点,如果我们能通过它的出边更新邻点(所有点初始被初始化为0,起点初始化为1),也就是把C的乘积变得更小,就操作并把这个点加入队列中,因此spfa求最短路、最长路都是可以的。
代码实现:
#include<iostream>
#include<queue>
using namespace std;
int n,m,A,B;
const int N=2010,M=2e5+10;
int e[M],ne[M],idx=1,h[N];
double w[M];
double dist[N];
bool st[N];
queue<int> q;
void add(int a,int b,double c){
e[idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx++;
}
void spfa(){
dist[B]=1;
st[B]=1;
q.push(B);
while(q.size()){
auto t=q.front();
q.pop();
st[t]=0;
for(int i=h[t];i;i=ne[i]){
int j=e[i];
if(dist[j]<dist[t]*w[i]){
dist[j]=dist[t]*w[i];
if(!st[j]) q.push(j),st[j]=1;
}
}
}
}
int main(){
cin>>n>>m;
for(int i=1;i<=m;i++){
int x,y,z;
cin>>x>>y>>z;
double c=(100.0-z)/100;//转化为我们分析中的c
add(x,y,c);
add(y,x,c);
}
cin>>A>>B;
spfa();//把B当作起点
printf("%.8lf",100/dist[A]);
return 0;
}
dijkstra:我们知道,dijkstra是基于贪心思想的,对于求最短路问题,我们先找出距离终点最短的一个,初始时就是它自己,然后用它更新别的点,但是它不能求最长路问题(这里的证明我也不会,《算法导论》上应该有),而对于我们要求最大的C,我们每次找出最大的C来更新,初始时也是它自己,然后用相似的方法来更新,我们发现也能AC。(在这里我有个猜测,dijkstra不能求最长路是因为初始时到终点最近的不是终点自己。)具体证明我是真的不会,我太菜了,留下了不争气的泪水 。
代码实现:(我不想再写了,就直接贴y总的了)
#include <iostream>
using namespace std;
const int N = 2010;
int n, m, S, T;
double g[N][N];
double dist[N];
bool st[N];
void dijkstra(){
dist[S] = 1;//起点初始化为1,其它点都是0
for (int i = 1; i <= n; i ++ ){
int t = -1;
//找出最大的C
for (int j = 1; j <= n; j ++ )
if (!st[j] && (t == -1 || dist[t] < dist[j]))
t = j;
st[t] = true;
for (int j = 1; j <= n; j ++ )
dist[j] = max(dist[j], dist[t] * g[t][j]);//更新
}
}
int main(){
scanf("%d%d", &n, &m);
while (m -- ){
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
double z = (100.0 - c) / 100;//转化为我们分析中的C
g[a][b] = g[b][a] = max(g[a][b], z);
}
cin >> S >> T;
dijkstra();
printf("%.8lf\n", 100 / dist[T]);
return 0;
}
顺带一提,对于本题,dijkstra:570ms左右,spfa:242ms左右。
最优乘车
题目链接:最优乘车
分析:这题的建图就有些技巧了,首先,这道题肯定有很多解法(我猜的),而我们既然是单源最短路练习,就用单源最短路求法来求吧。我们对于每一条路线,前面的一个点到后面的任意一个点的距离都设为1,然后我们求出起点到终点的最短路,再减去1就是答案(因为有一次是再同一条路线上转移的),不过还有一种特殊情况,起点和终点重合时,最短路是0,减去1就是-1了,因此我们要特殊处理一下。
代码实现:
//艹,怎么感觉我写的bfs一股spfa味
#include<iostream>
#include<sstream>
#include<queue>
#include<cstring>
using namespace std;
int n,m,tmp;
const int N=510,M=25010,INF=0x3f3f3f3f;
int e[M],ne[M],idx=1,h[N];
int stop[N];
int dist[N];
queue<int> q;
void add(int a,int b){
e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void bfs(){
memset(dist,0x3f,sizeof dist);
dist[1]=0;
q.push(1);
while(q.size()){
auto t=q.front();
q.pop();
for(int i=h[t];i;i=ne[i]){
int j=e[i];
if(dist[j]==INF){//因为是bfs,第一次更新时一定是最小值
dist[j]=dist[t]+1;
q.push(j);
}
}
}
}
int main(){
string s;
cin>>m>>n;
getline(cin,s);//吐掉\n,因为cin不会读取末尾的\0
for(int i=1;i<=m;i++){
getline(cin,s);
stringstream ss(s);//se存进ss中
int cnt=0;
while(ss>>tmp) stop[cnt++]=tmp;//把数字读入tmp中
for(int j=0;j<cnt;j++)
for(int k=j+1;k<cnt;k++)
add(stop[j],stop[k]);//加边
}
bfs();//因为边权为1,直接bfs
if(dist[n]==INF) puts("NO");//无解
else cout<<max(dist[n]-1,0)<<endl;
return 0;
}
昂贵的聘礼
题目链接:昂贵的聘礼
分析:最有意思的一题!读题都把我读晕了快。最难的还是考虑如何建图比较easy。不得不说这题的建图方法真是太牛逼了。。。我们人为设计一个虚拟起点,它到所有物品的距离都是这些物品的价格,然后再把能等效替代的物品相连,代价就是替代后所需的金币,然后这题就变成了一个最短路问题,起点是虚拟起点,终点是1,但是这题还有一个约束,就是等级差,我们枚举所有可能的等级范围,然后求最短路的同时只更新符合等级要求的点即可,至此,此题完美解决!
代码实现:
我们发现,只要我们会建图了,后面的随便写!
这是我写的堆优化dijkstra:117ms,可能因为边数比较多
#include<iostream>
#include<queue>
#include<cstring>
#define x first
#define y second
using namespace std;
typedef pair<int,int> PII;
const int N=110,M=1e4+10,INF=0x3f3f3f3f;
int e[M],ne[M],idx,h[N],w[M],level[N];
int m,n,res=INF;
int dist[N];
bool st[N];
void add(int a,int b,int c){
e[idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx++;
}
int dijkstra(int left){
priority_queue<PII,vector<PII>,greater<PII>> heap;
memset(st,0,sizeof st);
memset(dist,0x3f,sizeof dist);
dist[0]=0;
heap.push({0,0});
//刚开始多加了这一句st[0]=1,结果调了半天,气死了
while(heap.size()){
auto t=heap.top();
heap.pop();
int tmp=t.second;
if(st[tmp])continue;
st[tmp]=1;
for(int i=h[tmp];~i;i=ne[i]){
int j=e[i];
if(dist[j]>dist[tmp]+w[i]&&level[j]>=left&&level[j]<=left+m){//等级也符合要求才能更新
dist[j]=dist[tmp]+w[i];
heap.push({dist[j],j});
}
}
}
return dist[1];
}
int main(){
memset(h,-1,sizeof h);
cin>>m>>n;
for(int i=1;i<=n;i++){
int p,x;
cin>>p>>level[i]>>x;
add(0,i,p);
while(x--){
int t,v;
cin>>t>>v;
add(t,i,v);
}
}
for(int i=level[1]-m;i<=level[1];i++)//枚举等级区间的左边界,右边界是左边界+m,这里因为间接交易的双方等级差也不能超过m,因此等级区间的长度为m
res=min(res,dijkstra(i));
cout<<res<<endl;
return 0;
}
这是y总的朴素dijkstra:72ms
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 110, INF = 0x3f3f3f3f;
int n, m;
int w[N][N], level[N];
int dist[N];
bool st[N];
int dijkstra(int down, int up){
memset(dist, 0x3f, sizeof dist);
memset(st, 0, sizeof st);
dist[0] = 0;
for (int i = 1; i <= n + 1; i ++ ){
int t = -1;
for (int j = 0; j <= n; j ++ )
if (!st[j] && (t == -1 || dist[t] > dist[j]))
t = j;
st[t] = true;
for (int j = 1; j <= n; j ++ )
if (level[j] >= down && level[j] <= up)
dist[j] = min(dist[j], dist[t] + w[t][j]);
}
return dist[1];
}
int main(){
cin >> m >> n;
memset(w, 0x3f, sizeof w);
for (int i = 1; i <= n; i ++ ) w[i][i] = 0;
for (int i = 1; i <= n; i ++ ){
int price, cnt;
cin >> price >> level[i] >> cnt;
w[0][i] = min(price, w[0][i]);
while (cnt -- ){
int id, cost;
cin >> id >> cost;
w[id][i] = min(w[id][i], cost);
}
}
int res = INF;
for (int i = level[1] - m; i <= level[1]; i ++ ) res = min(res, dijkstra(i, i + m));
cout << res << endl;
return 0;
}