做了第一道差分约束系统的题后,趁热打铁再来一道。没想到这题就是纯粹的对短路径问题卡时间的题目。理解题意有需要注意的地方,我开始是SPFA(1),得到dis[n],再SPFA(n),得到dis[1],再取两者较大值。实际上只用算前者即可,dis[n]即为答案。
1. 刚开始直接写了SPFA的队列实现,不用说肯定是超时.
2. 听说栈实现SPFA会比较快,又改写成了栈,结果还是超时了,这就奇怪了,discuss都说栈能过啊。我用的是标准的图的邻接表存储,也就是链表实现的,但我看别人都用的数组来模拟的,我想是不是数组模拟的比较快?又改了,还是悲剧超时了.
3. 又发现我的代码有一点和别人不一样,我添加边是add(b,a,k),人家都是add(a,b,k),但我觉得应该没有影响啊,一个意思。我改成add(a,b,k)后就AC了(500+MS).
4. 再反过来改成我原先的标准图的邻接表存储,又超时了。这就让我有点想不通了,不是都说指针操作的效率比较高吗?怎么这就体现不出来呢,更何况这题时间上限是1.5S,用数组模拟就0.5S,用链表就超时....真想不通.
5. discuss里面说这题"点少边多,宜用Dij",普通的Dij时间复杂度是(n^2)肯定超时的,所以要用"优先队列"来实现每个循环中找最小的dis[i]值的过程,看人家都是用c++的STL中的priority_queue.悲剧的是我是c.所以得自己写堆实现优先队列了。研究了半天,写出来了。悲剧的是直接wrong了.
6. 纠结.....纠结......找bug....我找...找...终于找到一个c语言同胞.分析了一下代码确实有个难以发现的漏洞:每次找到一个最近的点,即为小根堆中的根Q[1],把改点从队列删除,此时必须跟新堆,即HeapAdjust(1),(这个过程我的程序没有问题),再松弛和该点相邻的点,松弛之后dis[]就发生了变化,必须再次更新堆(这个我掉了).....修补bug and AC (600+MS). oh...yes...
7. 这题又贡献了我30多个Submit。。。
我的SPFA(栈实现):
#include <stdio.h> #include <stdlib.h> #define INF 100000000 struct Edge { int e, v; } edge[150005]; int neg; int node[30005]; int next[150005]; int n; int dis[30005], vst[30005]; int Q[30005]; void add(int s, int e, int v) { edge[neg].e = e; edge[neg].v = v; next[neg] = node[s]; node[s] = neg++; } int relax(int s, int e, int v) { if (dis[s]+v < dis[e]) { dis[e] = dis[s]+v; return 1; } return 0; } /* 用栈来实现试试 */ int SPFA(int s0) { int i, t, p, top; for (i=1; i<=n; i++) dis[i] = INF; dis[s0] = 0; Q[0] = s0; top = 1; vst[s0] = 1; while (top) { t = Q[--top]; p = node[t]; while (p != -1) { if (relax(t, edge[p].e, edge[p].v) && !vst[edge[p].e]) { Q[top++] = edge[p].e; vst[edge[p].e] = 1; } p = next[p]; } vst[t] = 0; } return 1; } int main() { int m, k, a, b; memset(node, -1, sizeof(node)); scanf("%d %d", &n, &m); while (m--) { scanf("%d %d %d", &a, &b, &k); add(a, b, k); } SPFA(1); printf("%d/n", dis[n]); }
我的Dij+heap:
#include <stdio.h> #include <string.h> #include <stdlib.h> #define MAXV 30005 #define MAXE 150005 #define INF 1000000000 struct Edge { int e, v; } edge[MAXE]; int n, neg; int dis[MAXV], vst[MAXV]; int node[MAXV], next[MAXE]; int Q[MAXV]; int index[MAXV]; void add(int s, int e, int v) { edge[neg].e = e; edge[neg].v = v; next[neg] = node[s]; node[s] = neg++; } void swap(int x, int y) { int t; t = Q[x]; Q[x] = Q[y]; Q[y] = t; index[Q[x]] = x; index[Q[y]] = y; } void heapAdjust(int root) { int l, r, min; l = 2*root; r = 2*root+1; min = root; if (l <= Q[0] && dis[Q[l]] < dis[Q[min]]) min = l; if (r <= Q[0] && dis[Q[r]] < dis[Q[min]]) min = r; if (min != root) { swap(root, min); heapAdjust(min); } } void buildHeap() { int i; Q[0] = n; for (i=1; i<=n; i++) { Q[i] = i; index[i] = i; } for (i=n/2; i>=1; i--) heapAdjust(i); } /* 因为dis[Q[root]]变小了,要更新堆,使Q[root]沿着树枝往上移到合适的位置 */ void update(int root) { while (root/2 && dis[Q[root]] < dis[Q[root/2]]) { swap(root, root/2); root = root/2; } } int getMin() { int min; min = Q[1]; Q[1] = Q[Q[0]]; Q[0]--; index[Q[1]] = 1; heapAdjust(1); return min; } /* Dijkstra算法,优先队列实现 */ void Dijkstra(int s0) { int i, k, p; for (i=1; i<=n; i++) dis[i] = INF; dis[s0] = 0; buildHeap(); while (Q[0]) { k = getMin(); vst[k] = 1; p = node[k]; while (p != -1) { if (!vst[edge[p].e] && dis[k]+edge[p].v<dis[edge[p].e]) { dis[edge[p].e] = dis[k]+edge[p].v; update(index[edge[p].e]); /* index数组就是为这里而用,更新了dis[e],就要更新堆, 实际上不用更新整个堆,只用使e点沿着树枝往上移就行了, 因为dis[e]是变小了. */ } p = next[p]; } } } int main() { int m, k, a, b; memset(node, -1, sizeof(node)); scanf("%d %d", &n, &m); while (m--) { scanf("%d %d %d", &a, &b, &k); add(a, b, k); } Dijkstra(1); printf("%d/n", dis[n]); return 0; }