The Shortest Path in Nya Graph HDU4725 spfa||dijkstra

本文详细解析了HDU4725题目,介绍了两种解题思路:一种是通过拆分节点来构建图,另一种是直接进行节点抽象。同时分享了作者在实现过程中的经验教训,包括数据规模对算法效率的影响以及如何优化。

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

传送门:HDU4725

题意:有n个点,每个点属于不同的层,相邻的两层之间有边权值为C(假设为第一类边),某些点之间也有边(输入)(假设为第二类边)。问从1到n的最短路

这题真真切切的坑啊。。数据量超大,很卡时间,一开始自己写T了,看了题解发现思路就不对,因为我当初理解的是每层只有一个点,然而并不是。。

题解两种做法

1)先将每层抽象成两个点,个人理解拆成两个点是因为每个点有两个作用,①当第一类边的出点,②当第一类边的入点。因为第一类边在两个不同的层当中,所以要分出点和入点。

由此,我们可以自己决定0-n这n个点为同一层当中的各个点(即第二类边端点),i+n为第i层的的出点,i+2*n为第i层的入点。

2)加边,同一层的点之间的边就按输入加就好了(注意是无向边),两层之间的边就需要点抽象思维了,假设某一层为i,则要到i+1层得话应该加的边是i+n -->i+2*n+1,同样的,i+1层到i层应该加的边是i+n+1-->i+2*n,权值都为c。

这时候层与层之间有边了,同一层的点之间也有边了,但是注意点和层还没有建立关系!设点j在i层上,则点j到对应层i的边为j-->i+n。

然后就是基本的最短路的,注意由于数据量大,数组一定要开的够大,虽然网上说dijkstra+priority_queue或者spfa都能过但这种方法我只过了dijkstra。。spfa怎么改都是TLE。。

如果我说的还是不懂,建议去看一下这位大牛的博客  点击打开链接

//461ms  c++
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <algorithm>
#define MAXN 300005
#define inf 0x3f3f3f3f  
using namespace std;
struct Edge{
    int v,w;
    int next;
}edge[10000005];//一定要开的足够大
struct node{
    int v,w;
    friend bool operator<(node a,node b)
    {
        return a.w>b.w;
    }
};
int n,m,c,cnt=0;
int pre[MAXN],dis[MAXN],book[MAXN];
void add(int u,int v,int w)
{
    edge[++cnt].v=v;
    edge[cnt].w=w;
    edge[cnt].next=pre[u];
    pre[u]=cnt;
}
priority_queue<node>q;
void dijkstra()
{
    node t;
    while(!q.empty())
    q.pop();
    t.v=1;
    t.w=0;
    memset(dis,inf,sizeof(dis));
    memset(book,0,sizeof(book));
    dis[1]=0;
    q.push(t);
    while(!q.empty())
    {
        t=q.top();
        q.pop();
        int u=t.v;
        if(book[u])
        continue;
        book[u]=1;
        for(int i=pre[u];i!=-1;i=edge[i].next)
        {
            int v=edge[i].v;
            if(!book[v]&&dis[v]>dis[u]+edge[i].w)
            {
                dis[v]=dis[u]+edge[i].w;
                t.v=v;
                t.w=dis[v];
                q.push(t);
            }
        }
    } 
    if(dis[n]==inf||n==0)//注意n=0有坑。。
    printf("-1\n");
    else
    printf("%d\n",dis[n]);
}
int main()
{
    int t;
    scanf("%d",&t);
    for(int k=1;k<=t;k++)
    {
        int x,y,z,l;
        cnt=0;
        memset(pre,-1,sizeof(pre));
        scanf("%d%d%d",&n,&m,&c);
        for(int i=1;i<n;i++)
        {
            add(i+n,i+2*n+1,c);//层与层之间建边
            add(i+n+1,i+2*n,c);
        }
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&l);
            add(i,l+n,0);//层与点之间建边
            add(l+n*2,i,0);
        }
        for(int i=0;i<m;i++)
        {
            scanf("%d%d%d",&x,&y,&z);//点与点之间建边
            add(x,y,z);
            add(y,x,z);
        }
        printf("Case #%d: ",k);
        dijkstra();
    }
return 0;
}
接下来是第二种方法:

1)依然是把层抽象成点,但是不用拆点了,将层点编号为n+1-2*n。

2)在层与层之间建边,点与相邻层建边,同一层的点之间建边,还有层与在该层上的点建边。这里要开数组保存一下层与点之间的对应关系,因为只有相邻的两个层之间都有点才能建边。

网上都说这样建图运行时间短,但是我写的和上面的时间差不多啊,甚至还慢。。

这种方法看不懂建议看一下这位大牛的博客:点击打开链接

//513ms c++
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <algorithm>
#define MAXN 100005
#define inf 0x3f3f3f3f  
using namespace std;
struct node{
    int v,w;
    int next;
}edge[4000005];
int n,m,c,cnt=0;
int pre[2*MAXN],dis[2*MAXN],book[2*MAXN]; 
int L[2*MAXN],lay[2*MAXN];
void add(int u,int v,int w)
{
    edge[++cnt].v=v;
    edge[cnt].w=w;
    edge[cnt].next=pre[u];
    pre[u]=cnt;
}
void spfa()
{
    queue<int>q;
    while(!q.empty())
    q.pop();
    memset(dis,inf,sizeof(dis));
    memset(book,0,sizeof(book));
    dis[1]=0;
    q.push(1);
    book[1]=1;
    while(!q.empty())
    {
        int u=q.front();
        q.pop();
        book[u]=0;
        for(int i=pre[u];i!=-1;i=edge[i].next)
        {
            int v=edge[i].v;
            if(dis[v]>dis[u]+edge[i].w)
            {
                dis[v]=dis[u]+edge[i].w;
                if(!book[v])
                {
                    book[v]=1;
                    q.push(v);
                }
            }
        }
    } 
    if(dis[n]==inf||n==0)
    printf("-1\n");
    else
    printf("%d\n",dis[n]);
}
int main()
{
    int t;
    scanf("%d",&t);
    for(int k=1;k<=t;k++)
    {
        int x,y,z,l;
        cnt=0;
        memset(pre,-1,sizeof(pre));
        memset(L,0,sizeof(L));
        scanf("%d%d%d",&n,&m,&c);
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&lay[i]);
            L[lay[i]]=1;
        }
        for(int i=1;i<n;i++)
        {
            if(L[i]&&L[i+1])
            {
                add(n+i,n+i+1,c);//层与层建边
                add(n+i+1,n+i,c);
             } 
         }
          for(int i=1;i<=n;i++)//注意这次循环和上一次循环顺序不能颠倒,不然一定会T。。也不知道为啥。。求各路大神指教。
         {
             add(lay[i]+n,i,0);//层与点建边,注意方向和权值
            if(lay[i]>1)
            add(i,n+lay[i]-1,c);//点与相邻层建边
            if(lay[i]<n)
            add(i,n+lay[i]+1,c); 
         }
        for(int i=0;i<m;i++)
        {
            scanf("%d%d%d",&x,&y,&z);
            add(x,y,z);//点与点建边
            add(y,x,z);
        }
        printf("Case #%d: ",k);
        spfa();
    }
return 0;
}

这题学的就是建图的思想。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值