题目叙述:
给定P个点,T条边。约定起点是第0个点,终点是第P-1的点,求从起点到终点的所有最短路径的边的和乘以二。
难点:
有时候,存在多条路径共享多条边,那么在求和时,就只能加上不共享的边,而不是方案数乘以最短路径的长度。
关键:
标记走过的路径。
代码如下(具体有相应的注释):
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <vector>
#include <queue>
#include <cmath>
using namespace std;
#define INF 0x3f3f3f3f
#define ll __int64
const int maxn=10005;
ll sum;
struct Edge
{
Edge(int a,int b):to(a),wi(b){}
int to,wi;
};
vector<Edge> map[maxn];
vector<int> qian[maxn];
vector<int> path[maxn];
int dis[maxn],P;
bool vis[maxn],vis1[maxn];
void SPFA(int start)
{
queue<int> que;
// while(!que.empty()) que.pop();
memset(vis,0,sizeof(vis));
memset(dis,0x3f,sizeof(dis));
que.push(start);
vis[start]=1;
dis[start]=0;
while(!que.empty())
{
int top;
top=que.front();
que.pop();
vis[top]=0;
for(int i=0;i<map[top].size();i++)
{
int x=map[top][i].to,wi=map[top][i].wi;
if(dis[x]>dis[top]+wi)//更新最短权值
{
dis[x]=dis[top]+wi;
if(!vis[x])
{
vis[x]=1;
que.push(x);
}
qian[x].clear();
qian[x].push_back(top);//记录到该点的最短路径的前一个点
path[x].clear();
path[x].push_back(i);//记录该点容器的第几条边
}else if(dis[x]==dis[top]+wi)//如果有多条路径就记录多次
{
qian[x].push_back(top);
path[x].push_back(i);
}
}
}
}
void search(int last)
{
if(vis1[last]) { return;}
vis1[last]=1;//保证不重复计算同一条路径
for(int i=0;i<qian[last].size();i++)
{
int u=qian[last][i];
int v=path[last][i];
sum+=map[u][v].wi;
search(u);
}
}
int main()
{
// freopen("s","r",stdin);
int T;
while(scanf("%d%d",&P,&T)!=EOF)
{
sum=0;
for(int i=0;i<P;i++)//每次输入点和边,就把之前的容器全部清除
{
map[i].clear();
qian[i].clear();
path[i].clear();
}
for(int i=0;i<T;i++)
{
int p1,p2,l;
scanf("%d%d%d",&p1,&p2,&l);//输入边的两点以及权值
if(p1==p2) { continue;}//如果是自环,就没有必要记录,因为最短路径肯定没有自环
Edge x1(p2,l),x2(p1,l);//构建图
map[p1].push_back(x1);
map[p2].push_back(x2);
}
SPFA(0);//通过SPFA算出单源最短路径,并同时通过qian和path记录路径
memset(vis1,0,sizeof(vis1));
search(P-1);//求和
printf("%I64d\n",sum*2);//和的两倍
}
return 0;
}