sdut3562-求字典序最小的最短路 按顶点排序后spfa的反例
首先我们可以这么搞...倒序建图,算出源点s附近的点距离终点的距离,然后判断一下,终点是否能跑到源点
能跑到的话呢,我们就判断s周围的点是否在最短路上,然后我们选编号最小的点就好了
代码
#include <iostream> #include <cstdio> #include <algorithm> #include <queue> #include <vector> #include <cstring> using namespace std; const int maxn=1005; const int INF=1e9; int n,m,dist[maxn],pre[maxn];bool vis[maxn]; struct node{ int v,w;node(){};node(int v,int w):v(v),w(w){}; }; bool cmp(node a,node b){ return a.v<b.v; } vector<node> G[maxn]; vector<node> St; void init(){ for(int i=0;i<maxn;++i) G[i].clear();St.clear(); for(int i=0;i<maxn;++i) dist[i]=INF; memset(vis,0,sizeof(vis)); memset(pre,-1,sizeof(pre)); } int main(){ int T;scanf("%d",&T); while(T--){ scanf("%d%d",&n,&m); init();int a,b,c; for(int i=0;i<m;++i){ scanf("%d%d%d",&a,&b,&c); G[b].push_back(node(a,c)); if(a==0) St.push_back(node(b,c)); //G[b].push_back(node(a,c)); } queue<int> Q;Q.push(n+1);vis[n+1]=1;dist[n+1]=0; while(Q.size()){ int now=Q.front();Q.pop();vis[now]=0; for(int i=0;i<G[now].size();++i){ int to=G[now][i].v,tw=G[now][i].w; if(dist[to]>dist[now]+tw){ dist[to]=dist[now]+tw; pre[to]=now; if(!vis[to]) { Q.push(to);vis[to]=1; } } } } if(dist[0]==INF) { printf("-1\n");continue; } int flag=0,ans=INF; for(int i=0;i<St.size();++i){ int to=St[i].v,tw=St[i].w; if(tw+dist[to]==dist[0]){ if(to==n+1){ flag=1;break; } ans=min(ans,to); } } if(flag) printf("0\n"); else printf("%d\n",ans); } return 0; }
下面这种做法是错误做法,那就是先对每个邻接表按顶点标号大小排序,然后跑一遍spfa
一般的数据都能正常出解,但是对于这一组数据,哈哈,怕是翻车了
2 4
0 1 2
1 2 1
0 2 3
2 3 1
松弛完1,直接松弛0,2这条边,于是0,1,2,3这条路径不会被考虑了,
于是答案就是2,而不是正确的1,1被早来的0,2这条边从后面架空了
附上错误代码
#include <iostream> #include <cstdio> #include <algorithm> #include <queue> #include <vector> #include <cstring> using namespace std; const int maxn=1005; const int INF=1e9; int n,m,dist[maxn],pre[maxn];bool vis[maxn]; struct node{ int v,w;node(){};node(int v,int w):v(v),w(w){}; }; bool cmp(node a,node b){ return a.v<b.v; } vector<node> G[maxn]; void init(){ for(int i=0;i<maxn;++i) G[i].clear(); for(int i=0;i<maxn;++i) dist[i]=INF; memset(vis,0,sizeof(vis)); memset(pre,-1,sizeof(pre)); } int main(){ int T;scanf("%d",&T); while(T--){ scanf("%d%d",&n,&m); init();int a,b,c; for(int i=0;i<m;++i){ scanf("%d%d%d",&a,&b,&c); G[a].push_back(node(b,c)); //G[b].push_back(node(a,c)); } for(int i=0;i<maxn;++i){ if(G[i].size()) sort(G[i].begin(),G[i].end(),cmp); } queue<int> Q;Q.push(0);vis[0]=1;dist[0]=0; while(Q.size()){ int now=Q.front();Q.pop();vis[now]=0; for(int i=0;i<G[now].size();++i){ int to=G[now][i].v,tw=G[now][i].w; if(dist[to]>dist[now]+tw){ dist[to]=dist[now]+tw; pre[to]=now; if(!vis[to]) { Q.push(to);vis[to]=1; } } } } if(dist[n+1]==INF) { printf("-1\n");continue; } int i; for(i=0;i<G[0].size();++i) { if(G[0][i].v==n+1&&G[0][i].w==dist[n+1]) break; } if(i<G[0].size()) {printf("0\n");continue;} for(i=n+1;pre[i]!=0;i=pre[i]); printf("%d\n",i); } return 0; }
由于路径不算原点,所以说
0 2 3>0 1 2 3
也就是 2 3>1 2 3
所以在源点需要特判
然后还有一个要注意地方
有一种因为数据弱而能ac的错误做法,在求最短路的过程中,尝试使它的前驱变小
这样做的缺陷在于,你没法保留当前前驱大但是字典序小的路径,这种做法的代码如下
#include<iostream> #include<stdio.h> #include<stdlib.h> #include<string.h> #include<vector> #include<utility> #include<queue> using namespace std; #define mp make_pair #define X first #define Y second const int N=1008; const int M=1000000008; int n,m; vector<pair<int,int> >a[N]; int b[N],c[N],flag[N]; queue<int>d; int main(void) { int t,i,k,p1,p2,p3; scanf("%d",&t); while(t--) { scanf("%d%d",&n,&m); for(i=0;i<=n+1;i++)a[i].clear(); while(m--){scanf("%d%d%d",&p1,&p2,&p3);a[p1].push_back(mp(p2,p3));} for(i=0;i<=n+1;i++){b[i]=c[i]=M;flag[i]=0;} while(!d.empty()){d.pop();}d.push(0);b[0]=c[0]=0;flag[0]=1; while(!d.empty()) { k=d.front();d.pop();flag[k]=0; for(i=0;i<(int)a[k].size();i++) if((b[a[k][i].X]>b[k]+a[k][i].Y)||(b[a[k][i].X]==b[k]+a[k][i].Y&&c[a[k][i].X]>k)) { b[a[k][i].X]=b[k]+a[k][i].Y; c[a[k][i].X]=k; if(flag[a[k][i].X]==0){flag[a[k][i].X]=1;d.push(a[k][i].X);} } } if(b[n+1]==M){printf("-1\n");continue;} for(i=0;i<(int)a[0].size();i++)if(a[0][i].X==n+1&&a[0][i].Y==b[n+1])break; if(i<(int)a[0].size()){printf("0\n");continue;} k=n+1;while(c[k])k=c[k]; printf("%d\n",k); } return 0; }
除了逆向建图的做法之外,我们可以正向先跑出一个最短距离
然后把s的邻接点全部按编号排序,从s的邻接点挨个跑一遍s'到t的最短路,看该点在不在最短路上
找到的第一个解就是答案,附上代码
#include<iostream> #include<stdio.h> #include<stdlib.h> #include<string.h> #include<vector> #include<utility> #include<queue> #include<algorithm> using namespace std; #define mp make_pair #define X first #define Y second const int N=1008; const int M=1000000008; int n,m; vector<pair<int,int> >a[N]; int b[N],flag[N]; queue<int>d; int main(void) { int t,i,k,p1,p2,p3; scanf("%d",&t); while(t--) { scanf("%d%d",&n,&m); for(i=0;i<=n+1;i++)a[i].clear(); while(m--){scanf("%d%d%d",&p1,&p2,&p3);a[p1].push_back(mp(p2,p3));} for(i=0;i<=n+1;i++){b[i]=M;flag[i]=0;} while(!d.empty()){d.pop();}d.push(0);b[0]=0;flag[0]=1; while(!d.empty()) { k=d.front();d.pop();flag[k]=0; for(i=0;i<(int)a[k].size();i++)if(b[a[k][i].X]>b[k]+a[k][i].Y) { b[a[k][i].X]=b[k]+a[k][i].Y; if(flag[a[k][i].X]==0){flag[a[k][i].X]=1;d.push(a[k][i].X);} } } p3=b[n+1]; if(b[n+1]==M){printf("-1\n");continue;} for(i=0;i<(int)a[0].size();i++)if(a[0][i].X==n+1&&a[0][i].Y==b[n+1])break; if(i<(int)a[0].size()){printf("0\n");continue;} sort(a[0].begin(),a[0].end()); for(p1=0;p1<(int)a[0].size();p1++) { for(i=0;i<=n+1;i++){b[i]=M;flag[i]=0;} while(!d.empty()){d.pop();}d.push(a[0][p1].X);b[a[0][p1].X]=0;flag[a[0][p1].X]=1; while(!d.empty()) { k=d.front();d.pop();flag[k]=0; for(i=0;i<(int)a[k].size();i++)if(b[a[k][i].X]>b[k]+a[k][i].Y) { b[a[k][i].X]=b[k]+a[k][i].Y; if(flag[a[k][i].X]==0){flag[a[k][i].X]=1;d.push(a[k][i].X);} } } if(b[n+1]+a[0][p1].Y==p3)break; } printf("%d\n",a[0][p1].X); } return 0; }