floyd算法拓展

floyd

原理:

由上一层状态更新,可以去掉一维

应用:
  • 求任意两点间最短距离
  • 传递闭包问题(本人没认真学学校的离散课程)
  • 找最小环(区别spfa:spfa是找负环,floyd是找权值最小的环——正权边)
  • 恰好经过k条边的最短路(倍增的思想——不是很了解)
题单:
  1. 牛的旅行

关键:直径最大的牧场的直径尽可能小

思考:

(1)答案组成

  • 关于为什么大于等于连通块直径的最大值?
    • 想象一个连通牧场里面还有一个连通牧场,根据题意,显然两个牧场没有连通的边,那一定是外边的牧场的直径作为新牧场的直径
  • 经过新边的最长路径

(2)存储结构

//敲的第一遍
//未理解题意敲的
//没理解牧区的位置和邻接矩阵的关系

//woc,发现是先给牧区的位置坐标,然后用邻接矩阵描述两个牧区之间的连通关系
#include<bits/stdc++.h>

using namespace std;
const int N = 150,INF=0x3f3f3f3f;
int g[N][N];
double d[N][N],maxd[i];
char vtex[N][N];
int n;

void floyd(){
  memset(d,0x3f,sizeof d);
  
  for(int k=0;k<n;k++){
    for(int i=0;i<n;i++){
      for(int j=0;j<n;j++){
        d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
      }
    }
  }
}

double solve(){
  for(int i=0;i<n*n;i++){
    for(int j=0;j<n*n;j++){
      if(d[i][j]!=INF){
        maxd[i]=max(maxd[i],d[i][j]);
      }
    }
  }
  
  for(int i=0;i<n;i++){
    for(int j=0;j<n;j++){
      if(d[i][j]==INF)
    }
  }
}

signed main(){
  cin>>n;
  memset(g,0x3f,sizeof g)l;
  for(int i=0;i<n;i++){
    int x,y;
    cin>>x>>y;
    g[x][y]=g[y][x]=1;
  }
  
  for(int i=0;i<n;i++){
    cin>>g[i];
  }
  
  floyd();
  
  cout<<solve()<<endl;
  return 0;
}
//敲的第二遍
//我需要去存一个牧区的位置信息
#include<bits/stdc++.h>

#define x first
#define y second

using namespace std;
typedef pair<double,double> PDD;
const int N=150;
const double INF=1e20;
char g[N][N];
//maxd[i]存的是一个连通块中一个点到其他点最短距离的最大值
double d[N][N],maxd[N];
PDD vex[N];
int n;

double qdist(int i,int j){
  double dx=vex[i].x-vex[j].x;
  double dy=vex[i].y-vex[j].y;
  return sqrt(dx*dx+dy*dy);
}

void floyd(){
  
  for(int k=0;k<n;k++){
    for(int i=0;i<n;i++){
      for(int j=0;j<n;j++){
        d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
      }
    }
  }
  
}

double solve(){
  //res去存最终通过比较不同点在不同连通块的最大直径
  double res=0;	//求最大则res等于最小
  for(int i=0;i<n;i++){
    for(int j=0;j<n;j++){
      if(d[i][j]<INF){
        maxd[i]=max(maxd[i],d[i][j]);
        res=max(maxd[i],res);
      }
    }
  }
  
  double res1=INF;
  for(int i=0;i<n;i++){
    for(int j=0;j<n;j++){
      if(d[i][j]>=INF){
        double dist=qdist(i,j);
        res1=min(res1,dist+maxd[i]+maxd[j]);
      }
    }
  }
  
  //疑问:
  //如果求得若干牧场的中最大直径的牧场,嵌套的牧场其实还是外面的牧场的距离吗
  
  //想法:
  //我认为是这样的,因为在solve的第二个大循环里,是取最小值,如果嵌套类型还是会取外面牧场的直径
  
  return max(res,res1);
}

signed main(){
  cin>>n;
  //memset(d,0x3f,sizeof d);
  //i==j时是同一个点,需要等于0
  for(int i=0;i<n;i++){
    for(int j=0;j<n;j++){
      if(i!=j) d[i][j]=INF;
      else d[i][j]=0;
    }
  }
  
  for(int i=0;i<n;i++){
    cin>>vex[i].x>>vex[i].y;
  }
  
  for(int i=0;i<n;i++){
    cin>>g[i];
  }
  
  //在处理两个牧区之间距离的时候
  //由于是对称矩阵
  //我们只需要处理对称的部分即可
  //由于双向边,所以上面三句错误
  for(int i=0;i<n;i++){
    for(int j=0;j<n;j++){
      if(g[i][j]=='1'){
        d[i][j]=qdist(i,j);
      }
    }
  }
  floyd();
  
  printf("%.6lf\n",solve());
  return 0;
}
  1. 排序

传递闭包:

  • 所有能间接到的点直接连接
  • 与floyd关系
    • floyd能通过 n 3 n^3 n3的时间复杂度求得
    • 用邻接矩阵存储
    • 步骤:
      • 初始化(无权边)
      • 三重循环(更新一个条件—— d [ i ] [ k ] d[i][k] d[i][k] d [ j ] [ k ] d[j][k] d[j][k]存在的话,更新 d [ i ] [ j ] = 1 d[i][j]=1 d[i][j]=1——传递闭包的一个概念)
  • 与dp关系:
    • 和上一题的联系一致

思考:

  • 如何排序:
  • 如何确定某个点时最小的
  • 改进:
    • 添加一条边只需要 n 2 n^2 n2 的复杂度即可完成,不需要每次添加就floyd一次(增量算法)
      • 只判断(a,x)和(y,b)不能出效果,因为一条边的关系的加入需要精准传递到每一个点,(a,b)要建立联系,还需要判断,(a,y)和(x,b)
      • 可以不用floyd的原因,因为传递闭包中一条边只会影响和他相连两个点的关系,不相连的点的关系也做判断只会浪费时间,只会最多影响 n 2 n^2 n2条边(左边n,右边n)
#include<bits/stdc++.h>

using namespace std;
int n,m;
const int N=30,M=N*N;
int g[N][N],st[N],d[N][N];

int check(){
  for(int i=0;i<n;i++){
    if(g[i][i]) return 2;
  }
  
  for(int i=0;i<n;i++){
    for(int j=0;j<i;j++){
      if((!g[i][j]&&!g[j][i])){
        return 0;
      }
    }
  }
  return 1;
}

void get_min(){
  
  for(int i=0;i<n;i++){
    if(!st[i]){
      bool flag=1;
      for(int j=0;j<n;j++){
        if(!st[j]&&g[j][i]){
          flag=0;
          break;
        }
      }
      if(flag){
        st[i]=1;
        printf("%c",'A'+i);
      }
    }
  }
}

signed main(){
  while(cin>>n>>m,n||m){
    memset(g,0,sizeof g);
    int die,type=0;
    for(int t=1;t<=m;t++){
      char shi[5];
      cin>>shi;
      int a=shi[0]-'A',b=shi[2]-'A';
			if(!type){
        //当没有找到关系或者矛盾时才加边
        g[a][b]=1;
        for(int i=0;i<n;i++){
          if(g[i][a]) g[i][b]=1;
          if(g[b][i]) g[a][i]=1;
          for(int j=0;j<n;j++){
            if(g[i][a]&&g[b][j]){
              g[i][j]=1;
            }
          }
        }
        type=check();
        if(type) die=t;
      }
    }
    
    if(type==1){
      memset(st,0,sizeof st);
      printf("Sorted sequence determined after %d relations: ",die);
      for(int i=0;i<n;i++) get_min();
      puts(".");
    } 
    else if(type==2){
			printf("Inconsistency found after %d relations.\n",die);
    }
    else if(!type){
			puts("Sorted sequence cannot be determined.");
    }
  }
  return 0;
}
  1. 观光之旅

思考:

  • 怎么记录路径
  • 怎么去分类
    • 枚举中间点,找到以k为最大编号通过枚举i,j找到所有的环后就可以确定以k为最大编号的构成的环中的最小环
    • 如何做到不重不漏的
      • 因为floyd算法中,枚举到第k层时,d[i,j]在未更新前是不包含第k个点的,只有 1 ~ k − 1 1~k-1 1k1这些点,所以在1~k这些点中找到的环是不包含重(chóng)点的,
    • 为什么要枚举点的最大编号,如果枚举i,j时i,j不是小于k的编号能否达到一样的效果?
      • 枚举到n时也是对的,但是是冗余的,因为在做floyd算法时,最外层时枚举k,当到某一个k时,我们所能确定的是1~k-1点之间的最短路,就算此时我们找1~n中除了k的点i,j,也只有枚举的i,j时小于k的点才会更新,大于k的点也只是提前多一层更新,至于是不是最短路更新的,之有到k = = == ==n时才回显现,所以如果是在1~k-1枚举i,j,反而可以减少没必要的中间更新
#include<bits/stdc++.h>
//#define int long long
using namespace std;
const int N=110,M=2e4+10,INF=0x3f3f3f3f;
int g[N][N],d[N][N];
int n,m;
int path[N],cnt,pos[N][N];

void get_path(int i,int j){
  if(!pos[i][j])return ;
  int k=pos[i][j];
  get_path(i,k);
  path[cnt++]=k;
  get_path(k,j);
}

void dfloyd(){
  	memcpy(d,g,sizeof g);
 		int res=INF;
  	for(int k=1;k<=n;k++){
      
      for(int i=1;i<k;i++){
        for(int j=1;j<i;j++){
          //(d[i][j]+g[i][k]+g[k][j])和一个加 longlong不一样
          //括起来里面已经爆了
          if((long long)d[i][j]+g[i][k]+g[k][j]<res){
            res=(d[i][j]+g[i][k]+g[k][j]);
            cnt=0;
            path[cnt++]=k;
            path[cnt++]=i;
            get_path(i,j);
            path[cnt++]=j;
          }
        }
      }
      
      for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
          if(d[i][j]>d[i][k]+d[k][j]){
            d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
            pos[i][j]=k; //记录最短路被更新时的中间节点
            //通过递归一定能递归到两个点之间的边
          }
        }
      }
    }
  if(res==INF){
    puts("No solution.");
  }
  else{
    for(int i=0;i<cnt;i++){
      printf("%d ",path[i]);
    }
  }
}

signed main(){
  cin>>n>>m;
  memset(g,0x3f,sizeof g);
  for(int i=1;i<=n;i++){
    g[i][i]=0;
  }
  for(int i=0;i<m;i++){
    int x,y,z;
    cin>>x>>y>>z;
    g[x][y]=g[y][x]=min(g[x][y],z);
  }
  
  dfloyd();
  return 0;
}
  1. 牛站

思考:

  • 第一反应是bellman-ford算法
  • Bellman_ford算法和floyd在此题上的区别是什么
    • 是恰好和不超过的区别吗

在这里插入图片描述
在这里插入图片描述

听y话:

(1) 原始 d [ k , i , j ] d[k,i,j] d[k,i,j]含义:表示从i到j只经过1~k的话,最短路径是多少

(2) 本题含义:表示从i到j,恰好经过k条边的最短距离

  • 状态转移:

    • 类floyd算法,但是并不更新直接更新 g [ i ] [ j ] g[i][j] g[i][j],用 r e s [ i ] [ j ] res[i][j] res[i][j]去存储结果, g [ i ] [ j ] g[i][j] g[i][j]作为一个单位元去累加(也包含类快速幂思想)
#include<bits/stdc++.h>

using namespace std;
int n,t,m,s,e;
const int N=1000;
int g[N][N],res[N][N];
map<int,int> idx; //离散化数组

void mul(int c[][N],int a[][N],int b[][N]){
  static int temp[N][N];
  memset(temp,0x3f,sizeof temp);
  
  for(int k=0;k<n;k++){
    for(int i=0;i<n;i++){
      for(int j=0;j<n;j++){
        temp[i][j]=min(temp[i][j],a[i][k]+b[k][j]);
      }
    }
  }
  memcpy(c,temp,sizeof temp);
}

void qui(int k){
  memset(res,0x3f,sizeof res);
  for(int i=0;i<n;i++) res[i][i]=0;
  
  while(k){
    if(k&1) mul(res,res,g);
    k>>=1;
    mul(g,g,g);
  }
}

signed main(){
  cin>>t>>m>>s>>e;
  if(!idx.count(s)) idx[s]=n++;
  if(!idx.count(e)) idx[e]=n++;
  memset(g,0x3f,sizeof g);
  for(int i=0;i<m;i++){
    int a,b,c;
    cin>>c>>a>>b;
    if(!idx.count(a)) idx[a]=n++;
    if(!idx.count(b)) idx[b]=n++;
    a=idx[a],b=idx[b];
    g[a][b]=g[b][a]=min(g[a][b],c);
  }
  qui(t);
  cout<<res[idx[s]][idx[e]]<<endl;
  return 0;
}
  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值