题意:
给定节点数n,和边数m,边是单向边.
问从1节点出发到2,3,...n 这些节点路程和从从这些节点回来到节点1的路程和最小值。
思路:
很显然的最短路,先以1为起点进行一次最短路,然后再将边反向一下再以1为起点进行一下最短路。
这题的意义在于数据,一般的dijstra的O(N^2)显然没法过。
先用dijstra+heap试试。(以前被这个heap唬到了,其实heap直接用priority_queue实现即可)
//36412K 1829MS #include<iostream> #include<vector> #include<string> #include<queue> #include<algorithm> #define llong long long #define Abs(a) ((a)>0?(a):-(a)) #define Mod(a,b) (((a)-1+(b))%(b)+1) using namespace std; const int N=1000005; const int inf=1e10; int n,m; int a[N][3]; struct { int w,v,next; }edge[N]; int edgehead[N]; llong d[N]; bool vis[N]; int k; void addedge(int u,int v,int w) { edge[k].v=v; edge[k].w=w; edge[k].next=edgehead[u]; edgehead[u]=k++; } struct cmp//注意priority_queue的cmp写的和sort里面的cmp的区别,前者是struct,后者是函数,而且比较大小的关系刚好是相反的。 { bool operator()(const int a,const int b) { return d[a]>d[b]; } }; llong dijstra() { memset(vis,0,sizeof(vis)); priority_queue<int,vector<int>,cmp> que; for(int i=2;i<=n;i++) d[i]=inf; d[1]=0; que.push(1); while(!que.empty()) { int u=que.top(); vis[u]=true; que.pop(); for(int i=edgehead[u];i;i=edge[i].next)//遍历节点u的每条出去的边 { int v=edge[i].v; int w=edge[i].w; if(!vis[v]&&d[v]>d[u]+w)//如果是可松弛点即加入优先队列,d值越小越优先。 { d[v]=d[u]+w; que.push(v); } } } llong ans=0; for(int i=1;i<=n;i++) ans+=d[i]; return ans; } int main() { int cases; scanf("%d",&cases); while(cases--) { k=1; memset(edgehead,0,sizeof(edgehead)); memset(edge,0,sizeof(edge)); scanf("%d%d",&n,&m); for(int i=1;i<=m;i++) { scanf("%d%d%d",a[i],a[i]+1,a[i]+2); addedge(a[i][0],a[i][1],a[i][2]); } llong ans=dijstra(); k=1;//将图的边反向之后再来一遍 memset(edgehead,0,sizeof(edgehead)); memset(edge,0,sizeof(edge)); for(int i=1;i<=m;i++) { addedge(a[i][1],a[i][0],a[i][2]); } ans+=dijstra(); printf("%lld\n",ans); } return 0; }
下面是spfa的实现。
//36412K 1969MS #include<iostream> #include<vector> #include<string> #include<queue> #include<algorithm> #define llong long long #define Abs(a) ((a)>0?(a):-(a)) #define Mod(a,b) (((a)-1+(b))%(b)+1) using namespace std; const int N=1000005; const int inf=1e10; int n,m; int a[N][3]; struct { int w,v,next; }edge[N]; int edgehead[N]; llong d[N]; bool vis[N]; int k; void addedge(int u,int v,int w) { edge[k].v=v; edge[k].w=w; edge[k].next=edgehead[u]; edgehead[u]=k++; } llong spfa() { memset(vis,0,sizeof(vis)); for(int i=2;i<=n;i++) d[i]=inf; d[1]=0; queue<int> que; que.push(1); while(!que.empty()) { int u=que.front(); que.pop(); vis[u]=false;//注意spfa的vis和dijstra的不同点,前者是判断是否在queue里而已,后者是判断是否已经是最优的点。 for(int i=edgehead[u];i;i=edge[i].next) { int v=edge[i].v; int w=edge[i].w; if(d[v]>d[u]+w)//这里不能写成if(!vis[v]&&d[v]>d[u]+w),原因看上一行的注释 { d[v]=d[u]+w; if(!vis[v]) { que.push(v); vis[v]=true; } } } } llong ans=0; for(int i=1;i<=n;i++) ans+=d[i]; return ans; } int main() { int cases; scanf("%d",&cases); while(cases--) { k=1; memset(edgehead,0,sizeof(edgehead)); memset(edge,0,sizeof(edge)); scanf("%d%d",&n,&m); for(int i=1;i<=m;i++) { scanf("%d%d%d",a[i],a[i]+1,a[i]+2); addedge(a[i][0],a[i][1],a[i][2]); } llong ans=spfa(); k=1; memset(edgehead,0,sizeof(edgehead)); memset(edge,0,sizeof(edge)); for(int i=1;i<=m;i++) { addedge(a[i][1],a[i][0],a[i][2]); } ans+=spfa(); printf("%lld\n",ans); } return 0; }第一道spfa题,都说spfa速度比较快又好写,但是从这题看来,dijstra+heap其实也没难写多少。
总结一下我对spfa的理解:
1.本质是维护一个队列。
只要这个队列里面还有元素,即这个最短路还可松弛,即此时此图还存在“更短路”。
2.还要注意bool vis[]数组,它只用来判断某节点是否存在队列中,因为如果已经在队列中了则无需入队,入队只会造成不必要的时间损失。