之前很认真地看了用优先队列来实现Dijkstra这块,借鉴了小白书上的代码模板后,便拿这道题来试试水了。这道题的大意就是问你从地点1到地点2有多少条满足条件的路径(假设该路径经过 1->...-> a -> b ->...-> 2,那么d[b]必须小于d[a],其中d[b],d[a]分别是指 b,a 到地点2的最短距离),所以大体做法就是先求出以2为起点的单源最短路径,然后利用深搜dfs(v)表示 v顶点到2的满足以上条件的路径数,最后答案便是dfs(1),当然要加个记忆化。
因为我主要是拿来练习Dijkstra,所以深搜这部分可以不用变,先附上用邻接矩阵来实现的最原始的Dijkstra:
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 typedef long long LL; 6 const int INF= 0x3fffffff; 7 const int maxN= 1000; 8 9 int n, w[maxN+3][maxN+3]; 10 11 bool vis[maxN+2]; 12 int d[maxN+3]; 13 void Dijkstra(int u){ 14 memset(vis,0,sizeof(vis)); 15 for(int i=1; i<=n; ++i) 16 d[i]= INF; 17 d[u]= 0; 18 while(2){ 19 int x, m =INF; 20 for(int y=1; y<=n; ++y) 21 if(!vis[y] && m>d[y]) m= d[x=y]; 22 if(m==INF) break; 23 vis[x]= 1; 24 for(int y=1; y<=n; ++y) 25 if(w[x][y]!=INF) d[y]= min(d[y],d[x]+w[x][y]); 26 } 27 } 28 29 int dp[maxN+3]; 30 int dfs(int v){ 31 if(dp[v]!=-1) return dp[v]; 32 if(v==2) return 1; 33 dp[v]= 0; 34 for(int i=1; i<=n; ++i) 35 if(w[v][i]!=INF && d[i]<d[v]) dp[v]+= dfs(i); 36 return dp[v]; 37 } 38 39 int main(){ 40 int m,i,j,x,y,z; 41 while(scanf("%d",&n),n){ 42 scanf("%d",&m); 43 for(i=0; i<=maxN; ++i) 44 for(j=i; j<=maxN; ++j) 45 w[i][j]= w[j][i]= INF; 46 while(m--){ 47 scanf("%d%d%d",&x,&y,&z); 48 w[x][y]= w[y][x]= z; 49 } 50 Dijkstra(2); 51 memset(dp,-1,sizeof(dp)); 52 printf("%d ",dfs(1)); 53 } 54 return 0; 55 }
然后dfs部分保持不变,Dijkstra改用邻接表+优先队列来实现,小白书上也有比较详细的代码了,可是这道题是无向图,而小白书上的代码仅仅适用于有向图,需要作少许的更改(这一点点更改已经让我痛苦了好久T.T ,我不想再回忆了T.T)。
在邻接表的读入这部分调试了好久,因为对于题目中的每条边都需要手动把它拆成两条边再存进结点数组和边数组中,刘汝佳的书中读入和邻接表的预处理是合在一起的(后来才发现这对我调试带来了大大的不便),所以在最终的代码里我把它们分开来了,感觉代码清爽多了。
(因为dfs中需要访问两结点间是否存在边,如果单纯去遍历邻接表的话恐怕会超时,所以终究要用到一个邻接矩阵edge[][],只不过该矩阵用bool 来定义即可,因为只需记录两结点间是否有边(1/0),边权值已存放在了w[]数组中):
1 #include<cstdio> 2 #include<cstring> 3 #include<map> 4 #include<queue> 5 #include<vector> 6 #include<algorithm> 7 using namespace std; 8 const int INF= 0x3fffffff; 9 const int maxN= 1000; 10 const int maxM= 1000002; 11 12 int n,m; 13 bool edge[maxN+2][maxN+2]; 14 int first[maxN+2]; 15 int u[maxM+2], v[maxM+2], w[maxM+2], Next[maxM+2]; 16 17 //边读入邻接表中: 18 void read_graph() 19 { 20 memset(first,-1,sizeof(first)); 21 22 //一开始我是这个读法,把所有的边整体复制到原始边的最后面,思路是没有错, 23 //但就是因为少了 v[e+m]= u[e]的复制足够让我几乎想破了脑袋也想不出来, 24 //因为当时是凌晨2点多做的,头脑不清醒以为边的终点 v[e]用不上所以不用复制, 25 //但Dijkstra中是所有的信息都要用到的,包括 起点,终点,边权值,三者缺一不可! 26 /* for(int e=0; e<m; ++e) 27 { 28 scanf("%d%d%d",u+e,v+e,w+e); 29 u[e+m]= v[e]; 30 v[e+m]= u[e]; //就是少了这个,结果错成狗! 31 w[e+m]= w[e]; 32 edge[u[e]][v[e]]= edge[v[e]][u[e]]= true; 33 }*/ 34 35 //然后这种读法是每读入一条边,就把它紧凑地复制到下一个位置,注意 e每次循环是加 2的 36 for(int e=0; e<2*m; e+=2) 37 { 38 scanf("%d%d%d",u+e,v+e,w+e); 39 u[e+1]= v[e]; 40 v[e+1]= u[e]; //记得也要有这个,不然同样错成狗! 41 w[e+1]= w[e]; 42 edge[u[e]][v[e]]= edge[v[e]][u[e]]= true; 43 44 /* Next[e]= first[u[e]]; //一边读入一边处理也可以, 45 first[u[e]]= e; 46 47 Next[e+1]= first[u[e+1]]; 48 first[u[e+1]]= e+1;*/ 49 } 50 51 //把邻接表的处理放在后面就更清晰,代码更清爽。 52 for(int e=0; e<2*m; ++e) 53 { 54 Next[e]= first[u[e]]; 55 first[u[e]]= e; 56 } 57 } 58 59 //优先队列所需的一切数据结构 60 typedef pair<int,int> pii; 61 priority_queue<pii, vector<pii>, greater<pii> > q; 62 bool done[maxN+2]; 63 int d[maxN+2]; 64 65 void Dijkstra(int u){ 66 memset(done,0,sizeof(done)); 67 for(int i=0; i<=n; ++i) 68 d[i]= INF; 69 d[u]= 0; 70 q.push(pii(d[u],u)); 71 while(!q.empty()){ 72 int x= q.top().second; q.pop(); 73 if(done[x]) continue; 74 done[x]= 1; 75 for(int e= first[x]; e!= -1; e= Next[e]) 76 if(d[x]!=INF && d[v[e]] > d[x]+w[e]){ 77 d[v[e]]= d[x]+w[e]; 78 q.push(pii(d[v[e]],v[e])); 79 } 80 } 81 } 82 83 int dp[maxN+2]; 84 int dfs(int v){ 85 if(dp[v]!=-1) return dp[v]; 86 if(v==2) return 1; 87 dp[v]= 0; 88 for(int i=1; i<=n; ++i) 89 if(edge[v][i] && d[i]<d[v]) dp[v]+= dfs(i); 90 return dp[v]; 91 } 92 93 int main(){ 94 while(scanf("%d",&n),n){ 95 scanf("%d",&m); 96 memset(edge,0,sizeof(edge)); 97 read_graph(); 98 Dijkstra(2); 99 memset(dp,-1,sizeof(dp)); 100 printf("%d ",dfs(1)); 101 } 102 return 0; 103 }