6-08. 城市间紧急救援

#include<vector>
#include<iostream>
using namespace std;
const int N=500,INF=1<<30;
int n,m,src,dst,pre[N],dis[N],team[N],gather[N],used[N],path[N];
struct road{int to,len;road(int a,int b):to(a),len(b){}};
vector<road>adj[N];

void  dijkstra(){
  //initialization
  fill_n(dis,n,INF);dis[src]=0;
  fill_n(pre,n,-1);
  path[src]=1,gather[src]=team[src];
  while(true){
    // find a new city next
    int next=-1,mmin=INF;
    for(int i=0;i<n;++i)
      if(!used[i] && dis[i]<mmin)
         mmin=dis[next=i];
    if(next==-1)break;
    used[next]=true;
    // relax the edges starting from next
    for(auto x:adj[next]){
      int to=x.to,len=x.len;
      if(dis[next]+len<dis[to]){
        path[to]=path[next];
        pre[to]=next;
        dis[to]=dis[next]+len;
        gather[to]=gather[next]+team[to];
      }else if(dis[next]+len==dis[to]){
        path[to]+=path[next];
        if(gather[to]<gather[next]+team[to]){
          gather[to]=gather[next]+team[to];
          pre[to]=next;}//if
      }//else if
    }//for
  }//while
}//dijkstra
void printpath(int k){
  if(pre[k]!=-1)printpath(pre[k]);
  static bool first=true;
  if(first)first=false;
  else cout<<' ';
  cout<<k;
}
int main(){
  cin>>n>>m>>src>>dst;
  for(int i=0;i<n;++i)cin>>team[i];
  while(m--){
    int a,b,c;cin>>a>>b>>c;
    adj[a].emplace_back(b,c);
    adj[b].emplace_back(a,c);
  }
  dijkstra();
  cout<<path[dst]<<' '<<gather[dst]<<endl; 
  printpath(dst);
  return 0;
}
dijkstra算法每个点会收敛一次,每条边会松弛一次,每个点的收敛是一个循环,dis[ ]在每轮迭代前后表示从原点到各点的已知最短距离,pre[ ]表示从原点到个点最短路径上的前一节点,path[ ]表示到每个节点的最优路径的条数,对一条路径<a,b>,松弛它后能得到两种结论:一是通过<a,b>到达b的路径(这条路径之前肯定没检查过,因为每条路径只会被松弛一次)比当前到达b的路径更短,二是它同样短,更短会造成path[b] = path[a],同样短则有path[b]+=path[a],同样短的路径根据其他条件决定哪个是更好的路径。

算法并不是套用在问题上,“定义并使用定义”才是用算法解决问题的思维,尤其是在循环表达式里使用定义,这里对path ,dis ,pre 这几个数组的定义及循环表达式前后的保持是关键。经典的dijkstra算法也只是使用了一个简单的循环表达式而已。就像所有节点最短路径的floyd算法,它的正确性是从DP角度解释的,但硬要从DP角度理解十分困难,但从不变式角度理解就很简单,定义dis[i][j]是当前已知的从i到j的最短路径值,pre[i][j]是i~>j的这条最短路径中j节点的前驱结点,算法保持住了这个性质。这样理解简单很多。

你可能感兴趣的:(6-08. 城市间紧急救援)