SPFA算法之一
//poj 1511 Invitation Cardstle
//有p个点和e条边,从第一个点出发,求到每个点再回来所需要的最短路之和。边是有向边。
//1 <= p,e<= 1000000 显然邻接矩阵是不可能存下来的,所以用邻接表
//先正向建边求一次最短路径(从顶点1到其他顶点),再反向建边再求一次最短路径,两次求和
#include<iostream>
using namespace std;
struct Edge
{
int index;
int weight;
Edge *next;
}Vertex[1000002];
const int MaxWeight=10000000002;
int n,p,e,i,j,table[1000002][3];
__int64 sum;
void graph(int tag)
{
for(i=1;i<=p;++i)
Vertex[i].next=NULL;
for(i=1;i<=e;++i)
{
int u=table[i][0],v=table[i][1],w=table[i][2];
if(tag==1)
swap(u,v);
Edge *q=new Edge;
q->index=v;q->weight=w;
q->next=Vertex[u].next;Vertex[u].next=q; //始终都是在前面插入结点
}
}
bool S[1000002];
__int64 distD[1000002];
int deq[1000002]; //spfa 队列
void spfa(int u)
{
fill(distD,distD+p+1,MaxWeight);
distD[u]=0;
memset(S,0,sizeof(S[0])*(p+1));
S[u]=1;
int head=0,rear=0,w;
deq[0]=1;
while(head<=rear)
{
int v=deq[head++];
S[v]=0;
Edge *curr=Vertex[v].next;
while(curr!=NULL)
{
int vv=curr->index;w=curr->weight;
if(distD[v]+w<distD[vv])
{
distD[vv]=distD[v]+w;
if(!S[vv])
S[vv]=1,deq[++rear]=vv; //要注意deq申请的空间可能不够,因为每个点可以多次压进队列
//这道题不会出现负环,所以不必考虑某个点进入队列的次数是否超过N次
//假如要判断是否出现负环,可以设一数组cnt[]记录顶点入队列的次数,超过N次则跳出
}
curr=curr->next;
}
}
for(i=2;i<=p;++i) //根结点下标是1
sum+=distD[i];
}
int main()
{
scanf("%d",&n);
while(n--)
{
scanf("%d%d",&p,&e);
for(i=1;i<=e;++i)
scanf("%d%d%d",&table[i][0],&table[i][1],&table[i][2]);
sum=0;
graph(0);
spfa(1);
graph(1);
spfa(1);
printf("%I64d\n",sum);
}
return 0;
}
/*
求单源最短路的SPFA算法的全称是:Shortest Path Faster Algorithm
建立一个队列,初始时队列里只有起始点,
在建立一个表格记录起始点到所有点的最短路径
(该表格的初始值要赋为极大值,该点到他本身的路径赋为0)。
然后执行松弛操作,用队列里有的点去刷新起始点到所有点的最短路,
如果刷新成功且被刷新点不在队列中则把该点加入到队列最后。重复执行直到队列为空
判断有无负环:如果某个点进入队列的次数超过N次则存在负环(SPFA无法处理带负环的图)
时间复杂度O(ke), 其中k为所有顶点进队的平均次数,可以证明k一般小于等于2。
SPFA算法是Bellman-Ford算法的一种队列实现,减少了不必要的冗余计算,
他的基本算法和Bellman-Ford一样,并且用如下的方法改进:
1、第二步,不是枚举所有节点,而是通过队列来进行优化
设立一个先进先出的队列用来保存待优化的结点,优化时每次取出队首结点u,
并且用u点当前的最短路径估计值对离开u点所指向的结点v进行松弛操作,
如果v点的最短路径估计值有所调整,且v点不在当前的队列中,就将v点放入队尾。
这样不断从队列中取出结点来进行松弛操作,直至队列空为止。
2、同时除了通过判断队列是否为空来结束循环,还可以通过下面的方法:
判断有无负环:如果某个点进入队列的次数超过V次则存在负环(SPFA无法处理带负环的图)。
*/