Poj 3159 (差分约束系统,SPFA+栈,Dij+heap)

   做了第一道差分约束系统的题后,趁热打铁再来一道。没想到这题就是纯粹的对短路径问题卡时间的题目。理解题意有需要注意的地方,我开始是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;  
}


你可能感兴趣的:(c,算法,struct,存储,语言)