最短路总结与拓展

最短路的拓展
  • 在求多个点到一个点的距离时,我们可以将边反向,做一次最短路即可。
  • 在写SPFA时,有一个简单的小优化,SLF优化,如果要进队的点的dist小于队首的dist,则将其加在队首 http://www.cnblogs.com/cj695/archive/2012/07/27/2611215.html
  • 在判断是否存在负权环时,我们可以在SPFA中加入used[]数组,每当j被松弛一次,used[j]++,if used[j]>n ,那么存在负权环(如果不放心的话,可以写>n+1)
  • 插句题外话,floyd多好用的
  • 我们可以使用修改后的floyd求传递闭包(可以求解一些连通性问题,判定任意两点见是否有路;也可以用来求有向无环图的根->然而我觉得没有luan用)
for(int k=1;k<=n;k++)//枚举中间点
  for(int i=1;i<=n;i++)//枚举起点
   for(int j=1;j<=n;j++)//枚举终点
     f[i][j]=f[i][j] ||(f[i][k] && f[k][j]);


  • 可以使用floyd求有向和无向带权图的最小环问题
    • 先来看有向图:
      • 令f[i][i]=inf , 经过floyd后,求得的f[i][i]即为最小环长度
      • 但是O(n^3)的时间复杂度总是不那么令人满意的
      • 可以枚举起点s,新增点s',若存在边(u,s),则新增一条边(u,s'),求s到s'的最短路即可。所有最短路中最短的一条即是最小环,用SPFA或者heap+dijkstra均可通过。
      • 用DFS求有向带权图图的最小环似乎也是可以的,枚举起点即可,加点剪枝,应该还是比floyd好。
    • 那么无向图的最小环呢?
      • 仔细观察Floyd的三层循环,发现中间点枚举的顺序是从小到大,也就是当枚举到某个点k,对所有小于它的点对i和j,从i到j的最短路是不经过k的,如果此时有边g[i,k]和g[k,j]则可形成一个k->i->....->j->k的环,再用k去松弛所有的点对,以便下次枚举k+1时重新找与1~k之间的点形成的环,找出所有环中最小的。g[][]表示原图中各点之间距离,dist[i][j]表示i,j之间最短距离

for(int k=1; k<= n; k++)
{
    for(int i=1; i<=k-1; i++) //求最小环
        for(int j=i+1; j<=k-1; j++)
            if(dist[i][j]!=INF&&g[i][k]!=INF&&g[k][j]!=INF)
                ans=min(ans,dist[i][j]+g[i][k]+g[k][j]);
    for(int i=1; i<=n; i++) //求最短路
        for(int j=1; j<=n; j++)
            if(dis[i][k]!=INF&&dis[k][j]!=INF)
                dist[i][j]=min(dist[i][j],dist[i][k]+dist[k][j]);
} 


  • 还可以使用floyd算法来求任意两点间的最短路径条数,g表示条数
if(d[i][j]==d[i][k]+d[k][j])g[i][j]+=g[i][k]*g[k][j];//相等

if(d[i][k]+d[k][j]<d[i][j])//修改
{ 
    d[i][j]=d[i][k]+d[k][j];
    g[i][j]=g[i][k]*g[k][j];
}

  • 那么如果我们是求给定两点
    间的最短路径条数,floyd就不太理想了,我们可以使用
    • 用BFS实现——边权长度为1的情况,O(n);
    • Dijkstra算法。时间复杂度O(n^2); 关键语句如下:
      • if(d[j]==d[k]+a[k][j])g[j]+=g[k];           
      • if(d[j]>d[k]+a[k][j]){d[j]=d[k]+a[k][j];g[j]=g[k];}
    • SPFA算法。时间复杂度O(2E);
      • SPFA的处理要稍微复杂一些,如果我们还像上面那样,就会出错,因为SPFA会反复优化,一个点进队多次,如果直接累加的话肯定会加多
      • 故用dlt[i]表示当前在队列中以元素i结尾的路径条数,每次要弹队首时(即这个点已经优化完了目前它所能优化的点),将dlt清零
 vis[v0]=1;d[v0]=0;dlt[v0]=1;q.push(v0);
    while(!q.empty())
    {
        int fr=q.front();
        q.pop();vis[fr]=0;
        for(int i=h[fr];i;i=w[i].nxt)
        {
            int j=w[i].to;
            if(d[j]>=d[fr]+w[i].val)
            {
                if(d[j]==d[fr]+w[i].val)
                {
                    g[j]+=dlt[fr];
                    dlt[j]+=dlt[fr];
                }
                else
                {
                    d[j]=d[fr]+w[i].val;
                    g[j]=dlt[j]=dlt[fr];
                }
                if(!vis[j])
                {
                    vis[j]=1;
                    q.push(j);
                }
            }
        }
        dlt[fr]=0;
    }


  • 求从S出发恰好经过K步(允许有重复边)到达E总共有多少走法该怎么做呢
    • 把给定的图转为邻接矩阵,即A[i,j]=1当且仅当存在一条边i->j。令C=A*A,那么C[i,j]=ΣA[i,k]*A[k,j],实际上就等于从点i到点j恰好经过2条边的路径数(枚举k为中转点)。类似地,C*A的第i行第j列就表示从i到j经过3条边的路径数。同理,如果要求经过k步的路径数,我们只需要二分求出A^k即可。
  • 求从S出发恰好经过K条边(允许有重复边)到达E的最短距离
    • 可以看这么一道例题 Cow Relays(POJ3613)BSOJ3114
      • 我们可以使用倍增+floyd(即用倍增法加速了状态转移)
      • 但是在余华程的论文《矩阵乘法在信息学中的应用》有对这道题使用矩阵的详细论证


  • 用修改的floyd算法求每两点间的k短路
    • •设d[i][j][L]为ij,只经过1..L的前k短路长度(向量)
    • •d[i][j][L] =Min{d[i][j][L-1],d[i][L][L-1] +d[L][L][L-1]* +d[L][j][L-1]
    • •两个k维向量A, B的运算
      • Min{A,B}:A和B共2k个数取前k个O(K)
      • A+B:A[i]+B[j]共k2个和取前k个O(KlogK)
      • A*=Min{0, A, 2A, 3A, …} O(K2logK)
    • •计算d[L][L][L-1]*的时间复杂度:O(nK2logK)
    • •计算状态d[i][j][L]: O(2KlogK+K)=O(KlogK)
    • •总时间复杂度:O(nK2logK+KlogKn3)
    • Floyd是一个优秀的算法,通过N次枚举中间点,将点i,j之间的距离松弛为最短。
  • 求从S到E第K短路(允许重复经过边)长度
    • 长度相同的路算同一条利用类AStar思想,以当前权值加上点到终点E的距离和(即在原图的逆图中跑SPFA先求出E到各点的距离)作为估价函数,用以决定其出队列的优先级别,当终点E出队列的距离和上次的不同,则计数器cnt++,若cnt==K则返回当前权值,一个剪枝是将cnt修改为cnt[] 同时记录其他各点出队列的有效次数(权值和上次相等的视为无效),若超过k,则不再放入队列,因为每个点的前面k个值足以形成从S到E的第K短路。
  • 求第二短路问题
    • 用dijkstra找出一条最短路径p1。
    • 由于第二最短路径至少有一条边和p1不同,所以可以这样求第二短路:枚举p1中的每一条边e1,将e1从图G中删除得到G’,然后再G’中求最短路,所有这些G’最短路中最短的为第二短路p2。
    • 注意:上述方法是边不能够重复走的次短路
    • 那么如果可以重复走呢?
      • 方法为先用Heap+Dijkstra(SPFA)求出1和N的单源最短路径,把无向边看成两个有向边,然后枚举每单向条边(u,v),计算Dist=dis(1,u) + dis(N,v)+map[u][v],看看此时Dist的值是否大于dis(1,N),如果是的话用它更新次短路径,保留一个最小的值。
  • 求a—b的最短路径经过C点的路径条数
    • 分别从A和C两点求单源最短路径;
    • 若A到B的最短路径=A到C的最短路径+C到B的最短路径,则ANS=g[A][C]*g[C][B]。
  • 路径减半问题
    • 从1开始Dijkstra(1,1),保存在d[1][i]中;
    • 从n开始Dijkstra(n,2),保存在d[2][i]中;
    • 枚举每条边(i,j),将其边权减少一半,看d[1][i]+w[i][j]/2+d[j][n]是否最小的。注意边是无向的,应该两个方向都要枚举。
    • 如果k次路径减半呢(可以作用于同一条边)
      • 分层图啊,少年
      • 拆点啊
  • 在一个给定的有向无环图中,求从开始顶点到结束顶点的最长路径(路径上的权值和)叫关键路径。
    • 大家都说拓扑排序后加DP
    • 但是我觉得做一次SPFA也挺好的
  • 对于有向图点权转边权问题
    • 图(a)是一个包含4个带权点的有向图。要把点权变成边权,首先将图中所有的边的权值设为0,然后将每个顶点拆成两个点vi和vi’,并添加一条有向边<vi’,vi>,权值为原顶点的权值。对于原图中的边,所有终点为点i的边在新图中连到vi’上,所有起点为i的边在新图中连到vi上,对于图(a)进行上述转化后变为图(b)。对于图(b),会发现所有的边<vi,vj’>都是0,由于对于每个点j,都有一条<vj’,vi>,现在把所有的点vj’拿掉,对于每条<vi,vj’>,新生成一条边<vi,vj>,权值等于<vi,vj’>+<vj’,vj>=0+<vj’,vj>=<vj’,vj>,由于vi’没有入边,所以暂时保留,但是更名为v0。对于图(b)进行如此操作后,得到图(c)。现在对比图(a)和图(c),可以发现,将点权转化为边权,就是直接对于每条边赋权值,该权值等于该边终点的点权,没有入边的点需要添加一个节点,并连接一条有向边,使之边权等于该点点权,对于图(c)来说,v0就是为了处理点v1而专门添加的点。如果有多个没有入边的点,只需要添加一个顶点,使之与所有没有入边的顶点相连即可。

    • 对于边权化点权的问题,我们不妨来看下面这一道题。
      3800 -- 【四校联考1】染色
      Description

        人人生而平等,然而后来人们逐渐选择了不同的道路,人与人之间,变得不再平等。你可能会因为染上恶人之色而堕落,成为社会的败类;你也可能因为染上善人之色,而在逆境中不断成长。但是,红与黑并非绝对的。所谓“近朱者赤,近墨者黑”,你能改变周遭的环境,那么周遭的环境也会影响你。在黑暗的人群中,善人之色是明亮的,而在明亮的人群中,善人之色的存在就会显得微薄。你的颜色,依靠着你自己的意志在选择。

        如今,你获得了一次重生,你要为自己,以及你周围的人们,重新染上一种你真正想要的颜色。具体来说,你的圈子里总共有N个人,每个人你可以选择为其重新染色,或者不染。对于第i个人,如果他/她被重新染色,那么这将会对社会带来wi的改变。同时,朋友关系也会对社会带来改变,如果i,j是朋友,那么他/她们会对社会带来的改变w(i,j)满足


      Input


      Output
      输出仅一行为一个整数,对社会产生的总的改变的最大值。
      Hint
      【数据规模与约定】
      对于40%的数据,M=0。
      对于另外40% 的数据,N,M≤20。
      对于所有数据,N,M≤10^5。

      第一次做的时候我就蠢了,最后20%妄图用分层图做,真是。。。
      其实由于这道题的特殊性,我们可以将边权加在点权上,那么再让ans-边权,最后再累加所有大于0的点权'
      对于这个贪心的正确性
      我们可以分类讨论以下情况:
      1. 两个点都选,减去多加的一个边权正好
      2. 两个点都不选,那么我们根据题意减去边权也是正确的
      3. 如果选择了其中一个点,那么由于不应加边权,所以减去一个边权也是正确的。
      • 实在是妙,但是这不是典型的,有一道典型的边权化点权我记不起来了。。。

  • 大致上有套路的最短路差不多就是这些,但是那有很多需要转化的,就下次再写了


  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值