图论-Floyd+分治-计蒜客A1108

题目链接:https://nanti.jisuanke.com/t/A1108 (百度地图的实时路况)

问题描述

给定 n 个点的有向图。定义 d(u, v, w) 为从 u 到 w 严格不经过
v 的最短路
,若路径不存在,则 d(u, v, w) = -1,求所有d(u,v,w)的和。(n<=300,1<=u,v,w<=n)

问题分析

  1. Floyd算法是将每个点k依次加入路径中进行计算最短路,但是与添加顺序无关。题目中是求依次去掉一个点时其它任意两点最短路之和,朴素的想法是暴力枚举每个点,用Floyd算法求去掉这一点后的最短路径。显然,这种方法的时间复杂度是O(n^4) $\to$ 10^8,必定超时
  2. 经过分析,可以看出暴力枚举中存在大量重复计算。比如对于图[1…4],挖去1时,需要计算的有{2,3,4};挖去2时,需要计算的有{1,34}。其中,{3,4}的计算对于1、2来说是重复的。
  3. Floyd的计算结果总是叠加在原图上的,想要传递经过点集S计算过的Floyd结果,就得把计算后的图传递。
  4. 为了达到优化目的,使用分治的思想,伪代码如下:
//solve(l,r,F)得到的是挖去[l..r]这些点得到的多源最短路径
void solve(l,r,F){
    if(l==r)//单点区间,即只挖去这一点l(r)
        sum_ans()
        
    P=F//继承父区间的Floyd结果
    floyd(l,mid,P)//利用其余点(左区间)来做Floyd优化
    solve(mid+1,r,P)//利用右区间Floyd结果来求解右区间问题
    
    P=F//继承父区间的Floyd结果
    floyd(mid+1,r,P)//利用其余点(右区间)来做Floyd优化
    solve(l,mid,P)//利用左区间Floyd结果来求解右区间问题
}
  1. 原始图中节点为[1…8],对分治做出图示如下:
1 2 3 4 5 6 7 8 =>
1 2 3 4=> // 5 6 7 8
5 6 7 8=> // 1 2 3 4
1 2=>5 6 7 8 // 3 4
3 4=>5 6 7 8 // 1 2
5 6=>1 2 3 4 // 7 8
7 8=>1 2 3 4 // 5 6
1=>5 6 7 8 / 3 4 // 2
2=>5 6 7 8 / 3 4 // 1
5=>1 2 3 4 / 7 8 //6
6=>1 2 3 4 / 7 8 // 5
J
K
N
O
  • 图中,以最下角的节点为例说明数字含义
    • 1代表该节点所示区间为1(特别地,这是个单点区间),表示该区间被排除在外
    • 5 6 7 8 / 3 4 // 2表示计算区间,其中//左边的是通过分治继承来的,//右边是当前位置计算

AC代码

#include <cstring>
#include <cstdio>
#include <algorithm>

using namespace std;
const int maxn=300+10,INF=0x3f3f3f3f;

struct Floyd
{
    int graph[maxn][maxn];
    Floyd(){memset(graph,INF,sizeof(graph));}
    int* operator[](int i){return graph[i];}
};

int n;
long long ans=0;

void solve(int l,int r,Floyd & F)
{
    if(l==r){
        for(int i=1;i<=n;i++){
            if(i!=l)for(int j=1;j<=n;j++){
                if(j!=l)ans+= F[i][j]==INF?-1:F[i][j];
            }
        }
        return;
    }
    int mid=l+((r-l)>>2);
    Floyd P=F;
    for(int k=l;k<=mid;k++)
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                P[i][j]=min(P[i][j],P[i][k]+P[k][j]);
    solve(mid+1,r,P);
    P=F;//承接父区间的Floyd结果
    for(int k=mid+1;k<=r;k++)
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                P[i][j]=min(P[i][j],P[i][k]+P[k][j]);
    solve(l,mid,P);
}

int main()
{
    Floyd mp;
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            scanf("%d",&mp[i][j]);
            if(mp[i][j]==-1)mp[i][j]=INF;
        }
    }
    solve(1,n,mp);
    printf("%lld\n",ans);
    return 0;
}

参考博客

  1. https://blog.csdn.net/vectorxj/article/details/70943539 (代码参考)
  2. https://blog.csdn.net/aircattle/article/details/52291488 (分析参考)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值