SCZ 20170812 T1 HKJ

因为题面实在是太过暴力,就不贴链接了……我自己重新写一下题面吧……

题目描述

给定一张带权有向图,设起点为1,终点为n,每个点除编号外还有一个序号,要求输出从起点至终点的最短路经过的点的序号和最短距离,如果两点之间有多条最短路,输出字典序最小的。

数据范围

对于30%的数据,\(2\leq n\leq 2\cdot 10^3,1\leq m\leq 4\cdot 10^3\)
对于60%的数据,保证数据随机。
对于100%的数据,\(2\leq n\leq 2\cdot 10^5,1\leq m\leq 4\cdot 10^5,1\leq w\leq 10^9\)。存在至少一条从1至n的最短路。


题解

30分做法

感觉这个部分分一点意思都没有啊……可能是为那些只会写\(O(n^2)\)dijkstra算法,连SPFA都不会的人准备的吧……

60分做法

由于不存在负权边,可以使用堆优化的dijkstra算法。由于要输出字典序最小的路径,我们需要记录从点1到点i的最短路中字典序最小的路径,这个可以用vector保存。在松弛时如果\(dis_{v}=dis_{u}+w_{u,v}\),就暴力对两条路径进行比较,最后更新为字典序较小的那个。由于每次松弛时复杂度最坏可能达到\(O(n)\),所以总复杂度最坏可能为\(O(n^{2}log_{n})\),一张网格图就可以轻松卡掉。然而前60%的数据均为随机,所以比较两路径的概率极小,所以复杂度还是偏向\(O(nlog_{n})\)的。

代码
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int maxn=2e5+10,maxm=4e5+10;
typedef pair<LL,int>pii;
priority_queue<pii,vector<pii>,greater<pii> >pq;
int heade[maxn],ev[maxm],ew[maxm],nexte[maxm];
int dw[maxn];
LL dis[maxn];
vector<int>pre[maxn];
int n,m,tot=0,s,t;
void add_edge(int u,int v,int w){tot++;ev[tot]=v;ew[tot]=w;nexte[tot]=heade[u];heade[u]=tot;}
int cmp(vector<int>a,vector<int>b)
{
    int i,flag,len1,len2;
    len1=a.size();len2=b.size();flag=len1<len2?0:1;
    for(i=0;i<min(len1,len2);i++){if(a[i]<b[i]){return 0;}else if(a[i]>b[i]){return 1;}}
    return flag;
}
void dijkstra()
{
    int i,ui,vi,wi;pii u;vector<int>tmp;
    dis[s]=0;pq.push(make_pair(dis[s],s));pre[s].push_back(dw[s]);
    while(!pq.empty())
    {
        u=pq.top();pq.pop();ui=u.second;
        for(i=heade[ui];~i;i=nexte[i])
        {
            vi=ev[i];wi=ew[i];tmp=pre[ui];
            if(dis[vi]<dis[ui]+wi){continue;}
            tmp.push_back(dw[vi]);
            if(dis[vi]>dis[ui]+wi)
            {
                dis[vi]=dis[ui]+wi;
                pre[vi]=tmp;
            }
            else if(!cmp(tmp,pre[vi])){pre[vi]=tmp;}
            pq.push(make_pair(dis[vi],vi));
        }
    }
}
int main()
{
    int i,j,u,v,w;
    cin>>n>>m;s=1;t=n;
    memset(heade,-1,sizeof(heade));memset(dis,127,sizeof(dis));
    for(i=1;i<=n;i++){scanf("%d",&dw[i]);}
    for(i=1;i<=m;i++){scanf("%d%d%d",&u,&v,&w);add_edge(u,v,w);}
    dijkstra();
    cout<<dis[t]<<endl;
    for(i=0;i<(int)pre[t].size();i++){printf("%d ",pre[t][i]);}cout<<endl;
    return 0;
}

100分做法

考虑到我们并不是要求出从起点到每个点的字典序最小的最短路径,所以在这方面进行优化。
先跑一遍堆优化dijkstra,求出从起点到每个点的最短路,然后枚举每条边,如果对于一条边\((u,v,w)\exists dis_{v}=dis_{u}+w\),那么这条边一定在从起点到终点的某条最短路中。我们相当于把其他的边删去,保留一个子图。真正的操作方法是在原有基础上构建一个新图,然后在新图上进行BFS或DFS,由于要求字典序最小,构图时最好使用vector,遍历前将每个点的邻接点根据序号进行排序,遍历时再记录每个点的前驱,这样搜到终点时就可以输出整条路径了。总复杂度\(O(nlog_{n}+m)\)

代码
#include<bits/stdc++.h>
#define LL long long
using namespace std;
typedef pair<LL,int>pii;
priority_queue<pii,vector<pii>,greater<pii> >pq;
const int maxn=2e5+10,maxm=4e5+10;
int heade[maxn],eu[maxm],ev[maxm],ew[maxm],nexte[maxm];
LL dis[maxn];
int dw[maxn],pre[maxn],isvis[maxn],rec[maxn];
struct dot{int id,d;};
vector<dot>link[maxn];
int n,m,tot=0,s,t,num=0,flag=0;
int cmp(dot a,dot b){return a.d<b.d;}
void add_edge(int u,int v,int w){tot++;eu[tot]=u;ev[tot]=v;ew[tot]=w;nexte[tot]=heade[u];heade[u]=tot;}
void dijkstra()
{
    int i,ui,vi,wi;pii u;
    dis[s]=0;pq.push(make_pair(dis[s],s));
    while(!pq.empty())
    {
        u=pq.top();pq.pop();ui=u.second;
        for(i=heade[ui];~i;i=nexte[i])
        {
            vi=ev[i];wi=ew[i];
            if(dis[vi]<=dis[ui]+wi){continue;}
            dis[vi]=dis[ui]+wi;
            pq.push(make_pair(dis[vi],vi));
        }
    }
    printf("%lld\n",dis[t]);
}
void dfs(int ui)
{
    int i,vi,di;
    if(flag){return;}
    isvis[ui]=1;
    if(ui==t)
    {
        flag=1;
        for(i=t;i!=s;i=pre[i]){rec[++num]=dw[i];}rec[++num]=dw[s];
        for(i=num;i>=1;i--){printf("%d ",rec[i]);}cout<<endl;
        return;
    }
    for(i=0;i<(int)link[ui].size();i++)
    {
        if(flag){break;}
        vi=link[ui][i].id;di=link[ui][i].d;
        if(isvis[vi]){continue;}
        pre[vi]=ui;
        dfs(vi);
    }
}
int main()
{
    int i,j,u,v,w;
    cin>>n>>m;s=1;t=n;
    memset(heade,-1,sizeof(heade));memset(dis,127,sizeof(dis));
    for(i=1;i<=n;i++){scanf("%d",&dw[i]);}
    for(i=1;i<=m;i++){scanf("%d%d%d",&u,&v,&w);add_edge(u,v,w);}
    dijkstra();
    for(i=1;i<=m;i++)
    {
        u=eu[i];v=ev[i];w=ew[i];
        if(dis[u]+w==dis[v]){link[u].push_back((dot){v,dw[v]});}
    }
    for(i=1;i<=n;i++){sort(link[i].begin(),link[i].end(),cmp);}
    dfs(s);
    return 0;
}

转载于:https://www.cnblogs.com/XSC637/p/7754560.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值