实现思路
由于是求最短路,我想到了用前向星+SPFA的解法
在求解的过程中注意考虑当两条路的长度相同时,选择累积的消防队最多的那条路
但是提交后:
只通过了第一个测试点
上网找了相关SPFA解法的博客
利用SPFA算法解决PAT A1003
全网最全!PAT甲组1003.Emergency (优先队列实现迪杰斯特拉算法,Bellman算法,SPFA)思路与注意点–补充《算法笔记》
重要警示!!!
不要再脑子不清楚的情况下瞎做题,我一直认为自己的思路没有问题,原来是我没注意题目想要的结果是什么,结果要的不是最短路的长度,而是最短路出现的次数
在SPFA计算相同长度路径出现的次数:
具体的过程,结合当前的例子来说。
如图所示,设定起点为1,终点为6。
变量的设置:num数组中存储的是起点到达该点的最短路的条数,
——————————————————————————————
从左到右依次可以看到num[3]=1,num[2]=1
能连接到点4的点有1,2,5但其中能满足最短路到点4的路径的点只有3和2
故num[4]=num[2]+num[3]=2
再看能到达7的点的有4和6,且由4和6到达的路径均为最短路
所以num[7]=num[4]+num[6]=3
——————————————————————————————
概括就是统计最短路径到达该点的前一点,累加满足这样条件的前一点的路径数
Bellman实现
如果是使用Folyed-Bellman做法的不需要考虑那么多直接使用的规则是,当最短路更新时,num[v]=num[u],当最短路不变,只不过是通过别的u点到达的
令num[v]=num[v]+num[u]
SPFA实现
如果是SPFA方式的需要辅助一个set集合,当有点到达v的最短路不变时,将新的点放入set集合中再统一进行运算
两种方法在实现的过程中有区别的原因主要是在于:
这样做的原因在于,SPFA运算的过程出了队列的点时有可能再次进入到队列中,而进行Bellman做法时点时不可能再进入到队列中
参考代码(Floyed-Bellman)
#include <iostream>
#include <queue>
using namespace std;
typedef long long ll;
const int inf = 99999999;
const int maxn = 505;
int n, m, c1, c2;
int weight[maxn], e[maxn][maxn], w[maxn], num[maxn], dis[maxn];
int visit[maxn];
int main()
{
for (int i = 0;i < maxn;i++)
dis[i] = inf;
for (int i = 0;i < maxn;i++)
{
for (int j = 0;j < maxn;j++)
e[i][j] = inf;
}
cin >> n >> m >> c1 >> c2;
for (int i = 0;i < n;i++)
cin >> weight[i];
int t1, t2, t3;
for (int i = 0;i < m;i++)
{
cin >> t1 >> t2 >> t3;
e[t1][t2] = e[t2][t1] = t3;
}
dis[c1] = 0;
w[c1] = weight[c1];
num[c1] = 1;
for (int i = 0;i < n;i++)
{
int k = -1, minn = inf;
for (int j = 0;j < n;j++)
{
if (!visit[j] && dis[j] < minn)
{
k = j;
minn = dis[j];
}
}
if (k == -1)
break;
visit[k] = 1;
for (int v = 0;v < n;v++)
{
if (!visit[v] && e[k][v] != inf)
{
if (dis[v] > dis[k] + e[k][v])
{
dis[v] = dis[k] + e[k][v];
num[v] = num[k];
w[v] = w[k] + weight[v];
}
else if (dis[v] == dis[k] + e[k][v])
{
num[v] = num[k] + num[v];
if (w[v] < w[k] + weight[v])
{
w[v] = w[k] + weight[v];
}
}
}
}
}
cout << num[c2] << " " << w[c2];
return 0;
}
参考代码(SPFA)
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<vector>
#include<set>
#include<map>
#include<stack>
#include<queue>
using namespace std;
const int N = 510;
const int INF = 0x3fffffff;
struct Node
{
int v, dis;
Node(int _v, int _dis) : v(_v), dis(_dis) {
}
};
vector<Node> Adj[N];
int n, m, st, ed, weight[N];
int d[N], w[N], num[N];
set<int> pre[N];
bool vis[N] = {
false};
bool SPFA(int s)
{
fill(d, d + N, INF);
memset(vis, false, sizeof(vis));
memset(num, 0, sizeof(num));
memset(w, 0, sizeof(w));
queue<int> Q;
Q.push(s);
vis[s] = true;
num[s] = 1;
w[s] = weight[s];
d[s] = 0;
while(!Q.empty())
{
int u = Q.front();
Q.pop();
vis[u] = false;
for(int i = 0; i < Adj[u].size(); i ++)
{
int v = Adj[u][i].v;
int dis = Adj[u][i].dis;
if(d[u] + dis < d[v])
{
d[v] = d[u] + dis;
w[v] = w[u] + weight[v];
num[v] = num[u];
pre[v].clear();
pre[v].insert(u);
if(!vis[v])
{
Q.push(v);
vis[v] = true;
}
}
else if(d[u] + dis == d[v])
{
if(w[u] + weight[v] > w[v])
{
w[v] = w[u] + weight[v];
}
pre[v].insert(u);
num[v] = 0;
for(set<int>::iterator it = pre[v].begin(); it != pre[v].end(); it ++)
{
num[v] += num[*it];
}
if(!vis[v])
{
Q.push(v);
vis[v] = true;
}
}
}
}
return true;
}
int main()
{
// freopen("test.txt","r",stdin);
cin >> n >> m >> st >> ed;
for(int i = 0; i < n; i ++)
{
cin >> weight[i];
}
int u, v, cost;
for(int i = 0; i < m; i ++)
{
cin >> u >> v >> cost;
Adj[u].push_back(Node(v, cost));
Adj[v].push_back(Node(u, cost));
}
SPFA(st);
cout<<num[ed]<<" "<<w[ed]<<endl;
return 0;
}
总结
要深刻理解不同最短路解法的细微差别,仔细读题不要想当然