poj2457 Part Acquisition(dijkstra||spfa+路径记录优化)

本文详细介绍了使用Dijkstra和SPFA算法解决最短路径问题的方法,并对比了两种算法的实现细节,包括如何初始化变量、存储路径及优化技巧。此外,还提供了使用Floyd算法求解最短路径的示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


http://poj.org/problem?id=2457

题意:m个关系n个货物,关系中的a b代表可以拿货物a换取货物b,构造一条最短路,起点1终点n,输出交换次数和路径。


思路:看了这个教程。注意是有向图。dijkstra和spfa事实上是一个思路,都是找到最短路后,沿着这条路根据找路时存储的前驱来存储相应的节点。记录路径的方式也有几种,刚开始用数组记录,绕的我头都晕了。后来用栈优化,一目了然。


这题后来也回顾过,好好想了想初始化问题。dijkstra和spfa其实都可以一开始就将dis数组置为无穷大,pre数组更新为-1。dijkstra中仅仅把dis[1]置为0,但是未访问,这样所有的点都能算上。spfa中第一个点要入队列,dis[1]置为0,变成已访问,同样可以算上所有的点。

这样初始化就不会对起点进行一些对pre和dis的繁琐操作,使思路更加清晰。


首先来终极简化版本,以后做路径题方式就是以这个为参考:

#include <stdio.h>
#include <algorithm>
#include <stdlib.h>
#include <string.h>
#include <iostream>
#include <queue>
#include <stack>

using namespace std;

typedef long long LL;

const int N = 1005;
const int INF = 0x3f3f3f3f;

int dis[N], G[N][N], pre[N], ans[N], n;
bool vis[N];

void dijkstra(int s)
{
    memset(vis, false, sizeof(vis));
    memset(pre, -1, sizeof(pre));
    for(int i = 1; i <= n; i++)
        dis[i] = INF;
    dis[s] = 0;
    for(int i = 1; i <= n; i++)
    {
        int k = -1;
        for(int j = 1; j <= n; j++)
        {
            if(!vis[j] && (k==-1 || dis[j]<dis[k]))
                k = j;
        }
        if(k == -1) break;//已经遍历所有的点
        vis[k] = true;
        for(int j = 1; j <= n; j++)
        {
            if(!vis[j] && dis[k]+G[k][j]<dis[j])
            {
                dis[j] = dis[k]+G[k][j];
                pre[j] = k;
            }
        }
    }
}

void spfa(int s)
{
    memset(vis, false, sizeof(vis));
    memset(pre, -1, sizeof(pre));
    queue<int>que;
    for(int i = 1; i <= n; i++)
        dis[i] = INF;
    dis[s] = 0;
    vis[s] = true;
    que.push(s);
    while(!que.empty())
    {
        int now = que.front();
        que.pop();
        vis[now] = false;
        for(int i = 1; i <= n; i++)
        {
            if(dis[now]+G[now][i]<dis[i])
            {
                dis[i] = dis[now]+G[now][i];
                pre[i] = now;
                if(!vis[i])
                {
                    vis[i] = true;
                    que.push(i);
                }
            }
        }
    }
}

void Print()
{
    stack<int>path;
    int now = n, cnt = 0;
    if(dis[n] == INF) printf("-1\n");
    else
    {
        while(now != -1)
        {
            path.push(now);
            now = pre[now];
            cnt++;
        }
        printf("%d\n", cnt);
        while(!path.empty())
        {
            printf("%d\n", path.top());
            path.pop();
        }
    }
}

int main()
{
  //  freopen("in.txt", "r", stdin);
    int m, s, e;
    scanf("%d%d", &m, &n);
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= n; j++)
        {
            if(i == j) G[i][j] = 0;
            else G[i][j] = INF;
        }
    for(int i = 1; i <= m; i++)
    {
        scanf("%d%d", &s, &e);
        G[s][e] = 1;
    }
  //  dijkstra(1);
    spfa(1);
    Print();
    return 0;
}


然后是这个把我绕晕了的数组记录路径:


#include <stdio.h>
#include <algorithm>
#include <stdlib.h>
#include <string.h>
#include <iostream>
#include <queue>
#include <stack>

using namespace std;

typedef long long LL;

const int N = 1005;
const int INF = 0x3f3f3f3f;

int dis[N], G[N][N], pre[N], ans[N], n;
bool vis[N];

void dijkstra(int s)
{
    memset(vis, false, sizeof(vis));
    memset(pre, -1, sizeof(pre));
    for(int i = 1; i <= n; i++)
    {
        dis[i] = G[s][i];
        if(dis[i]!=INF && i!=s) pre[i] = s;
    }
    dis[s] = 0;
    vis[s] = true;
    for(int i = 1; i <= n; i++)
    {
        int k = -1;
        for(int j = 1; j <= n; j++)
        {
            if(!vis[j] && (k==-1 || dis[j]<dis[k]))
                k = j;
        }
        if(k == -1) break;//已经遍历所有的点
        vis[k] = true;
        for(int j = 1; j <= n; j++)
        {
            if(!vis[j] && dis[k]+G[k][j]<dis[j])
            {
                dis[j] = dis[k]+G[k][j];
                pre[j] = k;
            }
        }
    }
}

void spfa(int s)
{
    memset(vis, 0, sizeof(vis));
    memset(pre, -1, sizeof(pre));
    queue<int>que;
    for(int i = 1; i <= n; i++)
        dis[i] = INF;//初值记得赋无穷,而不是G数组中的值!
    dis[s] = 0;
    vis[s] = true;
    que.push(s);
    while(!que.empty())
    {
        int now = que.front();
        que.pop();
        vis[now] = false;//记得还原!!
        for(int i = 1; i <= n; i++)
        {
            if(dis[now]+G[now][i]<dis[i])
            {
                dis[i] = dis[now]+G[now][i];
                pre[i] = now;
                if(!vis[i])
                {
                    vis[i] = true;
                    que.push(i);
                }
            }
        }
    }
}

void print()
{
    memset(ans, 0, sizeof(ans));
    int cnt = 0;
    if(dis[n] == INF)
        printf("-1\n");
    else
    {
        ans[cnt++] = n;
        int tmp = pre[n];
        while(tmp != -1)
        {
            ans[cnt++] = tmp;
            tmp = pre[tmp];
        }
        printf("%d\n", cnt);
        for(int i = cnt-1; i >= 0; i--)
            printf("%d\n", ans[i]);
     //   printf("sad");
    }
}

int main()
{
   // freopen("in.txt", "r", stdin);
    int m, s, e;
    scanf("%d%d", &m, &n);
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= n; j++)
        {
            if(i == j) G[i][j] = 0;
            else G[i][j] = INF;
        }
    for(int i = 1; i <= m; i++)
    {
        scanf("%d%d", &s, &e);
        G[s][e] = 1;
    }
//    dijkstra(1);
    spfa(1);
    print();
    return 0;
}



后来用两种floyd敲了两次,都是TLE。大体思路就是二维数组在DP的时候已经将路径的前驱保存了下来,我们正好利用了这种特殊的性质。记录前驱就和前面思想一样,记录后继很方便,就是floyd的特权吧,然而复杂度太高,人家不让过哎= =


floyd记录前驱的路径记录法:

#include <stdio.h>
#include <algorithm>
#include <stdlib.h>
#include <string.h>
#include <iostream>
#include <queue>

using namespace std;

typedef long long LL;

const int N = 1005;
const int INF = 0x3f3f3f3f;

int G[N][N], pre[N][N], ans[N], n;

void floyd()
{
    for(int k = 1; k <= n; k++)
        for(int i = 1; i <= n; i++)
            for(int j = 1; j <= n; j++)
            {
                if(G[i][k]+G[k][j] < G[i][j])
                {
                    G[i][j] = G[i][k]+G[k][j];
                    pre[i][j] = pre[k][j];
                }
            }
    pre[1][1] = -1;
}

void print()
{
    memset(ans, 0, sizeof(ans));
    int cnt = 0;
    if(G[1][n] == INF)
        printf("-1\n");
    else
    {
        ans[cnt++] = n;
        int tmp = pre[1][n];
        while(tmp != -1)
        {
            ans[cnt++] = tmp;
            tmp = pre[1][tmp];
        }
        printf("%d\n", cnt);
        for(int i = cnt-1; i >= 0; i--)
            printf("%d\n", ans[i]);
    }
}

int main()
{
  //  freopen("in.txt", "r", stdin);
    int m, s, e;
    scanf("%d%d", &m, &n);
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= n; j++)
        {
            if(i == j) G[i][j] = 0;
            else G[i][j] = INF;
            pre[i][j] = i;
        }
    for(int i = 1; i <= m; i++)
    {
        scanf("%d%d", &s, &e);
        G[s][e] = 1;
    }
    floyd();
    print();
    return 0;
}




floyd记录后继的路径记录法:

#include <stdio.h>
#include <algorithm>
#include <stdlib.h>
#include <string.h>
#include <iostream>
#include <queue>

using namespace std;

typedef long long LL;

const int N = 1005;
const int INF = 0x3f3f3f3f;

int G[N][N], pre[N][N], n;

void floyd()
{
    for(int k = 1; k <= n; k++)
        for(int i = 1; i <= n; i++)
            for(int j = 1; j <= n; j++)
            {
                if(G[i][k]+G[k][j] < G[i][j])
                {
                    G[i][j] = G[i][k]+G[k][j];
                    pre[i][j] = pre[i][k];
                }
            }
    pre[n][n] = -1;
}

void print()
{
    int cnt = 0;
    if(G[1][n] == INF)
        printf("-1\n");
    else
    {
        printf("%d\n", G[1][n]+1);
        int tmp = pre[1][n];
        printf("%d\n", 1);
        while(tmp != -1)
        {
            printf("%d\n", tmp);
            tmp = pre[tmp][n];
        }
    }
}

int main()
{
  //  freopen("in.txt", "r", stdin);
    int m, s, e;
    scanf("%d%d", &m, &n);
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= n; j++)
        {
            if(i == j) G[i][j] = 0;
            else G[i][j] = INF;
            pre[i][j] = j;
        }
    for(int i = 1; i <= m; i++)
    {
        scanf("%d%d", &s, &e);
        G[s][e] = 1;
    }
    floyd();
    print();
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值