最短路综合

最短路综合


前言:

   最短路扩展例题。

 


例题:

  奇怪建图的最短路:

  POJ3767

  在一个国家有两个group,记做1和2,N个city,每个city属于1或者2。每两个city间有一定的距离,现在要从city1去city2,问最短的距离是多少,要求至多只有一次穿越时跨过分属不同group的city。city1总是属于group1,city2总属于group2。

  分析:同一城市内建双向边,city1到city2建单向边,注意无解。

  特殊最短路计数:

  洛谷P2505

  有向图,求每条边被多少条最短路经过

  分析:定理:任意一条最短路的某个子路径都是最短路(懒得证,脑补吧)

  枚举起点,先跑一遍dj(珍爱生命,远离spfa),把所有属于最短路的边标记,形成最短路图,必定是有向无环图。

  考虑到每一条边的贡献为两端端点的最短路条数相乘;

  拓扑排序,先求cnt1[ i ]表示从起点到 i 节点的最短路条数,易得如果cnt1[ st ]=1且存在边从u到v 那么cnt1[ v ] += cnt1[ u ];

  再求从cnt2[ i ]表示以 i 为起点的最短路条数,cnt2[ i ]初始值为1,若存在边从u到v,则cnt2[ u ]+=cnt2[ v ];

  每条边每次贡献为cnt1[ u ] * cnt2[ v ];

  POJ3613

  给定一张带权图,求从s到t的恰好经过k条边的最短路径长度

  分析:用一个矩阵表示初始经过一条边a_ij的长度,没有边是正无穷

  矩阵乘法:a矩阵表示经过n条边,b矩阵表示经过m条边,a*b矩阵则表示经过n+m条边,采用了floyd的思想

  矩阵快速幂即可

  证明:不会,但是会甩链接《矩阵乘法在信息学的应用》

  拓展:求s到t长度为k的方案数:

  矩阵a [ d ][ i ][ j ]为从i到j长度为d的方案数;

  分层图最短路:

  有一种图论模型:在图上可以进行k次特殊决策,每次决策不改变图的结构,只改变当前状态,我们称之为分层图最短路。

  洛谷P4568

  有k次免费机会的最短路

  分析:我们在存储每一个状态时多存储一个变量f表示当前已经使用了几次免费机会,dis[ i ][ j ]表示到 i 节点用了 j 次机会的最短路长度;

  每次决策分两部分:

  第一部分:不使用机会,直接莽过去;

  第二部分:使用机会,用特殊代价转移状态;

  

#include<bits/stdc++.h>
using namespace std;
#define int long long
inline int read()
{
    int x=0,f=1;
    char ch;
    for(ch=getchar();(ch<'0'||ch>'9')&&ch!='-';ch=getchar());
    if(ch=='-') f=0,ch=getchar();
    while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
    return f?x:-x;
}
int n,m,k,res=1e15;
int st,ed;
int dis[100010][21];
bool vis[10010][21];
int head[100010],cnt;
struct point
{
    int nxt,to,val;
}a[100010];
struct p
{
    int now,fre,dis;
    bool operator <(const p &a) const
    {
        return dis>a.dis;
    }
};
priority_queue<p> q; 
inline void add(int x,int y,int z)
{
    a[++cnt].nxt=head[x];
    a[cnt].to=y;
    a[cnt].val=z;
    head[x]=cnt;
}
inline void dj()
{
    memset(dis,0x3f,sizeof(dis));
    dis[st][0]=0;
    q.push((p){st,0,0});
    while(!q.empty())
    {
        int now=q.top().now;
        int f=q.top().fre;
        q.pop();
        if(vis[now][f]) continue;
        vis[now][f]=1;
        for(int i=head[now];i;i=a[i].nxt)
        {
            int t=a[i].to;
            if(f<k&&dis[t][f+1]>dis[now][f])
            {
                dis[t][f+1]=dis[now][f];
                if(t==ed)    res=min(res,dis[t][f+1]);
                q.push((p){t,f+1,dis[t][f+1]});
            }
            if(dis[t][f]>dis[now][f]+a[i].val)
            {
                dis[t][f]=dis[now][f]+a[i].val;
                if(t==ed) res=min(res,dis[t][f]);
                q.push((p){t,f,dis[t][f]});
            }
        }
    }
}
signed main()
{
    n=read(),m=read(),k=read();
    st=read(),ed=read();
    for(int x,y,z,i=1;i<=m;++i)
    {
        x=read(),y=read(),z=read();
        add(x,y,z);
        add(y,x,z);
    }
    dj();
    printf("%lld
",res);
return 0;
}

  洛谷P3831

  一个网格图,走一条边花费为2,在某些点转向花费为1,求最短路;

  分析状态:位置,方向。与其他最短路不同的就是方向。

  考虑到往回走肯定不优,所以我们只需要记录是横向还是纵向即可。

  考虑到n较大,我们只记录有用的节点(转向点和起点终点),将所有有用点记录在结构体中,设每个点的编号为(x-1)*n+y;

  1.按编号排序,离散化;

  2.按x排序,x相等按y排序,把x相等的相邻两个点之间连上边;

  3.按y排序,y相等按x排序,把y相等的相邻两个点之间连上边;

  跑dj(珍爱生命,远离spfa)

  (数据太水没有无法到达的情况,实际上注意判断,虽然我没判就过了)

  

#include<bits/stdc++.h>
using namespace std;
#define int long long
inline int read()
{
    int x=0,f=1;
    char ch;
    for(ch=getchar();(ch<'0'||ch>'9')&&ch!='-';ch=getchar());
    if(ch=='-') f=0,ch=getchar();
    while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
    return f?x:-x;
}
int n,m,res=1e18;
int stx,sty,edx,edy,tot,st,ed;
int dis[200010][3];
bool vis[200010][3];
int head[233333],cnt;
struct node
{
    int x,y,id;
}sub[200010];
struct p
{
    int dis,ch,now;
    bool operator<(const p &a) const
    {
        return dis>a.dis;
    }
};
priority_queue<p> q;
struct point
{
    int nxt,to,val;
    int ch;
}a[500010];
inline void add(int x,int y,int z,int ch)
{
    a[++cnt].nxt=head[x];
    a[cnt].to=y;
    a[cnt].val=z;
    a[cnt].ch=ch;
    head[x]=cnt;
}
inline bool cmp(node a,node b)
{
    return a.id<b.id;
}
inline bool cmp1(node a,node b)
{
    return (a.x==b.x)?a.y<b.y:a.x<b.x;
}
inline bool cmp2(node a,node b)
{
    return (a.y==b.y)?a.x<b.x:a.y<b.y;
}
inline void dj()
{
    memset(dis,0x3f,sizeof(dis));
    for(int i=1;i<=2;++i)
    {
        dis[st][i]=0;
        q.push((p){0,i,st});
    }
    while(!q.empty())
    {
        int now=q.top().now;
        int ch=q.top().ch;
        q.pop();
        if(vis[now][ch]) continue;
        vis[now][ch]=1;
        for(int i=head[now];i;i=a[i].nxt)
        {
            int t=a[i].to;
            int f=a[i].ch;
            if(dis[t][f]>dis[now][ch]+a[i].val+(f!=ch))
            {
                dis[t][f]=dis[now][ch]+a[i].val+(f!=ch);
                if(t==ed) res=min(res,dis[t][f]);
                q.push((p){dis[t][f],f,t});
            }
        }
    }
}
signed main()
{
    n=read(),m=read();
    for(int x,y,i=1;i<=m;++i)
    {
        x=read(),y=read();
        sub[++tot].x=x;
        sub[tot].y=y;
        sub[tot].id=(x-1)*n+y;
    }
    stx=read(),sty=read(),edx=read(),edy=read();
    sub[++tot].x=stx;
    sub[tot].y=sty;
    sub[tot].id=(stx-1)*n+sty;
    sub[++tot].x=edx;
    sub[tot].y=edy;
    sub[tot].id=(edx-1)*n+edy;
    sort(sub+1,sub+tot+1,cmp);
    for(int i=1;i<=tot;++i)
    {
        if(sub[i].id==(stx-1)*n+sty)
        {
            sub[i].id=i,st=i;
        }
        else if(sub[i].id==(edx-1)*n+edy)
        {
            sub[i].id=i,ed=i;
        }
        else sub[i].id=i;
    }
    sort(sub+1,sub+tot+1,cmp1);
    for(int i=1;i<=tot;++i)
    {
        if(sub[i-1].x==sub[i].x)
        {
            add(sub[i].id,sub[i-1].id,(sub[i].y-sub[i-1].y)*2,1);
            add(sub[i-1].id,sub[i].id,(sub[i].y-sub[i-1].y)*2,1);
        }
    }
    sort(sub+1,sub+tot+1,cmp2);
    for(int i=1;i<=tot;++i)
    {
        if(sub[i-1].y==sub[i].y)
        {
            add(sub[i].id,sub[i-1].id,(sub[i].x-sub[i-1].x)*2,2);
            add(sub[i-1].id,sub[i].id,(sub[i].x-sub[i-1].x)*2,2);
        }
    }
    dj();
    //-1
    printf("%lld
",res);
return 0;
}

  次短路

   洛谷P2865

  求严格次短路。

  方法1:dis[ i ][ 1 ]为到 i 节点最短路值,dis [ i ][ 2 ]为到 i 节点次短路值;

  这个用spfa,dj我每研究出按什么排序比较好

  

inline void spfa()
{
    for(int i=1;i<=n;++i) dis[i][1]=dis[i][2]=1e15;
    dis[1][1]=0;
    q.push(1);
    while(!q.empty())
    {
        int now=q.front();
        q.pop();
        vis[now]=0;
        for(int i=head[now];i;i=a[i].nxt)
        {
            int t=a[i].to;
            bool flag=0;
            if(dis[t][1]>dis[now][1]+a[i].val)
            {
                dis[t][2]=dis[t][1];
                dis[t][1]=dis[now][1]+a[i].val;
                flag=1;
            }
            if(dis[t][2]>dis[now][1]+a[i].val&&dis[t][1]!=dis[now][1]+a[i].val)
            {
                dis[t][2]=dis[now][1]+a[i].val,flag=1;
            }
            if(dis[t][2]>dis[now][2]+a[i].val)
            {
                dis[t][2]=dis[now][2]+a[i].val;
                flag=1;
            }
            if(flag&&!vis[t])
            {
                vis[t]=1;
                q.push(t);
            }
        }
    }
}

  方法2:求出最短路和路径,枚举删边;

  方法3:A*,不会;

  特殊最短路模型

  POJ3013

  给定一张无向图, 要求一棵最小生成树。 根结点确定为1,权值的定义为:每个结点都有自己的权值,每条边有长度, 构建每条边的花费是,此边的单价 * 所有后代的结点权重之和。

  转而考虑每个节点花费:该节点权值 * 节点到根的距离;

  从1号节点跑一遍最短路就行了

  妙啊

  基础图论大杂烩

  BZOJ4144(权限题,自己找吧qwq)

  n个点m条边的带权无向图,有s个点是加油站,每辆车有个油量上限c,保证x和y是加油站,能否从x走到y;

  分析:我们发现除了加油站,其他点都是垃圾都没有用,所以在加油站之间搞MST,然后货车运输就好了(别告诉我你不会货车运输

  怎么确定边权呢?

  我们记 f [ i ]为距离 i 号节点最近的加油站;

  考虑如果从x到y的路径上出现一个点k,f[ k ]属于z,那么从x到z,再用z到y一定更优;

  把所有加油站都扔进堆里面跑一遍dj,求出所有点的dis和 f ;

  枚举每一条边,如果有一条边两端的节点属于两个不同的加油站,那么那么就在待定最小生成树图上增加一条连接u,v的边,权值为dis[u]+dis[v]+len(u,v);

原文地址:https://www.cnblogs.com/knife-rose/p/11271688.html