luogu. p2865(次短路)

路障问题
贝茜选择的第二短的路径中,可以包含任何一条在最短路中出现的道路,并且,一条路可以重复走多次。当然咯,第二短路的长度必须严格大于最短路(可能有多条)的长度,但它的长度必须不大于所有除最短路外的路径的长度。
特殊样例
输入:
5 5
1 2 5
2 3 5
3 4 4
4 5 6
1 5 21
输出:
21
输入:
4 5
1 2 100
2 4 200
2 3 100
3 4 100
1 4 301
输出:
301
题意为:找出次短路
思路1
先跑两遍最短路,一个为从1到所有边的,一个为从n到所有边的
记录在d1,d2,数组里//
遍历所有的边(cnt条边),比较 该边(u到v)的权值+d1[u]+d2[v],若该值大于最短路d1[n],则符合题意,但要求找到次短路,就找这些值中的min//
d1[u]为从1到u的最短路,d2[v]为从n到v的最短路//

#include <bits/stdc++.h>
#define M(x,y) make_pair(x,y)
using namespace std;
typedef long long ll;
const int N=2e6+7;
const int inf=0x3f3f3f3f;
int f[N],to[N],nex[N],val[N],vis[N],d[N],from[N],pre[N],d1[N],d2[N];
int n,m,cnt,flag=1;
void lsctu(int a,int b,int c)
{
    from[++cnt]=a;
    to[cnt]=b;
    val[cnt]=c;
    nex[cnt]=f[a];
    f[a]=cnt;
}
priority_queue< pair<int,int> >q;
void dij(int s,int *d)//dij求最短路
{
    for(int i=1;i<=n;i++)
        d[i]=inf;
    memset(vis,0, sizeof(vis));
    d[s]=0;
    q.push(M(0,s));
    while(q.size())
    {
        int x=q.top().second;
        q.pop();
        if(vis[x]) continue;
        vis[x]=1;
        for(int i=f[x];i;i=nex[i])
        {
            int y=to[i];
            if(d[y]>d[x]+val[i]){
                d[y]=d[x]+val[i];
            q.push(M(-d[y],y));}
        }
    }
}
int main()
{
    cin>>n>>m;
    for(int i=1;i<=m;i++)
    {
        int x,y,z;
        cin>>x>>y>>z;
        lsctu(x,y,z);
        lsctu(y,x,z);
    }
    dij(1,d1);
    dij(n,d2);
    int ans=inf;
    for(int i=1;i<=cnt;i++)
    {
        if(d1[from[i]]+d2[to[i]]+val[i]>d1[n])//满足大于最短路
            ans=min(ans,d1[from[i]]+d2[to[i]]+val[i]);
    }
    cout<<ans<<endl;
    return 0;
}


思路2
基于luogu1186的思路 洛谷1186
先跑一遍最短路,在这一遍中获得pre数组,pre[y]=i 为记录到y点的边的编号//
然后遍历最短路的所有的边,根据1186题的思路,假设该边不能通过,即将其权值改为inf,再进行dij()操作看此时的最短路,一定比d[n]大,才满足题意,找出遍历的到最短路中的最小值即为所求//
需要注意一个点就是,路可以重复走,所以要同时考虑,原来最短路中的边走三次的情况(意思是再走一个来回),因为这中情况可能比那些还小//

#include <bits/stdc++.h>
#define M(x,y) make_pair(x,y)
using namespace std;
typedef long long ll;
const int N=2e6+7;
const int inf=0x3f3f3f3f;
int f[N],to[N],nex[N],val[N],vis[N],d[N],from[N],pre[N];
int n,m,cnt,flag=1;
void lsctu(int a,int b,int c)
{
    to[++cnt]=b;
    val[cnt]=c;
    nex[cnt]=f[a];
    from[cnt]=a;
    f[a]=cnt;
}
priority_queue< pair<int,int> >q;
void dij()
{
    for(int i=1;i<=n;i++)
        d[i]=inf;
    memset(vis,0, sizeof(vis));
    d[1]=0;
    q.push(M(0,1));
    while(q.size())
    {
        int x=q.top().second;
        q.pop();
        if(vis[x]) continue;
        vis[x]=1;
        for(int i=f[x];i;i=nex[i])
        {
            int y=to[i];
            if(d[y]>d[x]+val[i]){
                d[y]=d[x]+val[i];
                if(flag)pre[y]=i;//存最短路的边
                q.push(M(-d[y],y));}
        }
    }
}
int main()
{
    cin>>n>>m;
    for(int i=1;i<=m;i++)
    {
        int x,y,z;
        cin>>x>>y>>z;
        lsctu(x,y,z);
        lsctu(y,x,z);
    }
    dij();
    int mmm=d[n];//最短路
    flag=0;
    int ans=inf;
    for(int i=pre[n];i;i=pre[from[i]])
    {
        int x=val[i];
        val[i]=inf;
        dij();
        if(d[n]>mmm) {
            ans = min(ans, d[n]);
        }
        if(mmm+2*x>mmm)
        {
            ans = min(ans, mmm + 2 * x);//重复走的情况
        }
        val[i]=x;//复原该边
    }
    cout<<ans<<endl;
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值