SPFA算法之四
//poj 3013 Big Christmas Tree
#include<iostream> //邻接链表实现单源最短路SPFA
#include<deque>
using namespace std;
#define maxn 50002
const __int64 inf=(__int64)1<<63-1; //inf=4611686018427387904
//(1)#define inf (1<<63)-1 (2)const __int64 inf=(__int64)(1<<63)-1; (3)const __int64 inf=(1<<63)-1; 这三者的值都为-1
struct Edge
{
int index;
int weight;
Edge *next;
}Vertex[maxn];
int cases,v,e,i,j,edge[maxn][3],w[maxn];
void add_edge(int s,int t,int w)
{
Edge *q=new Edge; //每次都new 耗时间,跑了2391 MS
q->index=t;q->weight=w;
q->next=Vertex[s].next;Vertex[s].next=q; //不必检查重边,直接插入即可
}
bool S[maxn];
__int64 distD[maxn],sum; //这里要声明为__int64
void spfa(int u)
{
fill(distD,distD+v+1,inf);
distD[u]=0;
memset(S,0,sizeof(S[0])*(v+1));
S[u]=1;
int uu,vv,wei;
deque<int> col; //spfa 队列
col.push_back(u);
while(!col.empty())
{
uu=col.front();
col.pop_front();
S[uu]=0;
Edge *curr=Vertex[uu].next;
while(curr!=NULL)
{
vv=curr->index;wei=curr->weight;
if(distD[uu]+wei<distD[vv])
{
distD[vv]=distD[uu]+wei;
if(!S[vv])
{
S[vv]=1;
col.push_back(vv);
}
}
curr=curr->next;
}
}
for(i=1;i<=v;++i)
if(distD[i]==inf)
{
printf("No Answer\n");
return ;
}
sum=0;
for(i=1;i<=v;++i)
sum+=w[i]*distD[i];
printf("%I64d\n",sum);
}
int main()
{
scanf("%d",&cases);
while(cases--)
{
scanf("%d%d",&v,&e);
for(i=1;i<=v;++i)
scanf("%d",&w[i]);
for(i=1;i<=v;++i)
Vertex[i].next=NULL;
for(i=1;i<=e;++i)
{
scanf("%d%d%d",&edge[i][0],&edge[i][1],&edge[i][2]);
add_edge(edge[i][0],edge[i][1],edge[i][2]); //双向边
add_edge(edge[i][1],edge[i][0],edge[i][2]);
}
spfa(1);
}
return 0;
}
/*
要注意以下情况:
(1)当v=0,1时都是输出0,但不必另外加判断,
因为当v=0时,在决定是否No Answer的for循环for(i=1;i<=v;++i)本身就不会执行,sum=0
当v=1时,生成树中的根结点1的distD[1]=0,其他结点(若有的话)的distD也为0,sum+=w[i]*distD[i];sum仍然为0
(2)也不必另外筛除掉指向自己的边,因为在SPFA函数内的if(distD[v]+w<distD[vv]) 显然distD[v]=distD[vv],所以不成立,自然地这条边就被筛掉了
(3)重边的处理--另外,如果是邻接链表的SPFA(这道题v很大,也只能用邻接链表了),也不必特地筛除重边,
在求最短路径时当某边有更小值时,if(distD[v]+w<distD[vv])就会成立,相应的边会被更新的,所以最终还是用到这条边的最小值
事实上,如果在构图时对于新加进的边,如果与之前的有重复,取较小值,同样是可以AC的,如下
void add_edge(int s,int t,int w)
{
Edge *q=new Edge;
q->index=t;q->weight=w;
Edge *tmp=Vertex[s].next;
while(tmp!=NULL&&tmp->index!=t)
tmp=tmp->next;
if(tmp==NULL)
{
q->next=Vertex[s].next;Vertex[s].next=q; //没找到重边
}
else
tmp->weight=min(tmp->weight,w); //找到重边,取较小值
}
当然,如果是邻接矩阵就要判断重边了,取其中的最小值
(4)边是无向边
(5)要注意inf的取值,最短路径数组distD和结果sum 都要用__int64 存储
另外,不用deque,也可用循环队列,如下:
void spfa(int u)
{
fill(distD,distD+v+1,inf);
distD[u]=0;
memset(S,0,sizeof(S[0])*(v+1));
S[u]=1;
int uu,vv,wei;
int deq[maxn],head=0,rear=1;
deq[0]=u;
while(head!=rear) //循环队列
{
uu=deq[head++];
if(head==maxn)
head=0;
S[uu]=0;
Edge *curr=Vertex[uu].next;
while(curr!=NULL)
{
vv=curr->index;wei=curr->weight;
if(distD[uu]+wei<distD[vv])
{
distD[vv]=distD[uu]+wei;
if(!S[vv])
{
S[vv]=1;
deq[rear++]=vv;
if(rear==maxn)
rear=0;
}
}
curr=curr->next;
}
}
for(i=1;i<=v;++i)
if(distD[i]==inf)
{
printf("No Answer\n");
return ;
}
sum=0;
for(i=1;i<=v;++i)
sum+=w[i]*distD[i];
printf("%I64d\n",sum);
}
*/