1003. Emergency (25)

还有16天PAT考试最终要的是做透每一题

(1)

思路:两次dfs

第一次找到最短路径的长度

第二次在最短路径的前提下找到最大的救护资源数

#include <cstdio>
#include <climits>
#include <cstring>
#include <algorithm>
using namespace std;

const int M=550;
int n,m,c1,c2;
int res[M];
int v[M][M];
char vis[M];
int numpath=0;
int maxres=0;
int minpath=INT_MAX;

void dfs1(int c,int path) {
  if(c == c2) {
    minpath=min(path,minpath);
    return;
  }
  for(int i=0;i<n;i++) {
    if(i!=c && vis[i] == 0 && v[i][c] != INT_MAX) { //访问没有访问过的相联的其他节点
      vis[i]=1;
      dfs1(i,path+v[i][c]);
      vis[i]=0;
    }
  }
}

void dfs2(int c,int re,int path) {
  if(c == c2) {
    if(minpath == path) {
      maxres=max(maxres,re); 
      numpath++;
    }
    return;
  }
  for(int i=0;i<n;i++) {
    if(i!=c && vis[i] == 0 && v[i][c] != INT_MAX) { //访问没有访问过的相联的其他节点
      vis[i]=1;
      dfs2(i,re+res[i],path+v[i][c]);
      vis[i]=0;
    }
  }
}

int main() {
  scanf("%d %d %d %d",&n,&m,&c1,&c2);

  memset(res,0,sizeof(res));
  memset(vis,0,sizeof(vis));
  for(int i=0;i<M;i++){
    for(int j=0;j<M;j++){
      if(i == j) v[i][j]=0;
      else v[i][j]=INT_MAX;
    }
  }
  for(int i=0;i<n;i++) {
    scanf("%d",&res[i]);
  }
  //初始化图
  for(int i=0;i<m;i++) {
    int c3,c4,w;
    scanf("%d %d %d",&c3,&c4,&w);
    v[c3][c4]=w;
    v[c4][c3]=w;
  }

  vis[c1]=1;
  dfs1(c1,0);
  dfs2(c1,res[c1],0);
  printf("%d %d",numpath,maxres);
  return 0;
}

自己用的g++ + emacs的环境在写,这次dfs有个比较 本来是 == 不小心 写成了 =  ,偷懒没有加-Wall选项结果人肉debug了半天,以后得注意这一点才行

不过到时候考试用vs应该就不会出现这种情况吧

(2)

思路dijkstra算法

1.将dst各位初始位无穷dst[c1]为0

2.分为两个集合一个是已经确定最小距离的的点的集合P一个是还未确定最小距离的点Q

3.每次从Q中取最小的出来,它一定就是已经确定最小距离的点(原理如下(*))将它放入P

4.从该点取其相邻进行松弛

5.返回3.

(*)

                         

          s----------3---------v

             2                 /  -5

                        u

比如到u的最小距离,我们按照上面的做法会取2这条边因为它是s的边中最小的

如果在无负权的的情况下从其他位置转到u的一定会比这个长,但是这里有负权,结论就不成立了,这也是dijkstra不适合带负权的图的原因

#include <cstdio>
#include <cstring>
#include <climits>

using namespace std;

const int M=510;
int dst[M];
int book[M];
int res[M];
int num[M];
int weight[M];
const int inf=99999;
int ve[M][M];
int n,m,c1,c2;
int cnt=1;

void dij() {
  while(cnt < n) { 
    int mindst=inf;
    int v;
    for(int i=0;i<n;i++) {
      if(book[i] == -1) {
    if(dst[i] < mindst) {
      mindst=dst[i];
      v=i;
    }
      }
    }
    book[v]=0;
    cnt++;
    for(int i=0;i<n;i++) {
      if(ve[v][i] != inf && book[i] == -1) {
    if(dst[i] > dst[v]+ve[v][i]) {
      dst[i]=dst[v]+ve[v][i];
      num[i]=num[v];
      weight[i]=weight[v]+res[i];
    } else if(dst[i] == dst[v]+ve[v][i]){
      num[i]+=num[v];
      if(weight[v]+res[i] > weight[i])
        weight[i]=weight[v]+res[i];
    }
      }
    }
  }
}

int main() {
  scanf("%d %d %d %d",&n,&m,&c1,&c2);
  memset(res,0,sizeof(res));
  memset(book,-1,sizeof(book));
  memset(num,0,sizeof(num));
  memset(weight,0,sizeof(weight));
  //初始化图
  for(int i=0;i<M;i++){
    for(int j=0;j<M;j++){
      if(i == j) ve[i][j]=0;
      else ve[i][j]=inf;
    }
  }
  for(int i=0;i<n;i++) {
    scanf("%d",&res[i]);
    dst[i]=inf;
  }
  for(int i=0;i<m;i++) {
    int c3,c4,w;
    scanf("%d %d %d",&c3,&c4,&w);
    ve[c3][c4]=ve[c4][c3]=w;
    //      if(c3 == c1) dst[c4]=w;
    //      if(c4 == c1) dst[c3]=w; 不能加这两句,因为这会少两次松弛
    //使得num不能正确的初始化
  }

  dst[c1]=0;
  weight[c1]=res[c1];
  //  book[c1]=0; 不能加这一句不然刚开始就不是从c1开始的了
  num[c1]=1;
  dij();

  printf("%d",num[c2]);
  printf(" %d
",weight[c2]);
  return 0;
}

针对这一题关注一下 这里的num数组和weight数组

num[i]为节点i到源点的最短的路径的数量,weight[i]为节点i到源点的最短路径的最大资源数

dijkstra是用来计算单源最短路径的,这里我们还要计算最小路径的数量已经最小路径下最大的点权值

num和weight数组就是用来实现这一点的

考虑以下情况

(1) 某个点i的距离需要松弛  if(dst[i] > dst[v]+ve[v][i]) 

num[i]=num[v]

(2)某个点i的距离不需要松弛 dst[i] == dst[v]+ve[v][i]

num[i]+=num[v];

为什么这样可以得到

首先,若松弛后不是最小距离,num[i]以后一定会被替换掉,最后一定得到的是最小距离情况下的num

其次(2)的情况下,会加上新的路径数

同理weight数组在(1)的情况下最后一定会被最小距离情况的weight替换

而(2)的情况下如果weight值得到了更大的那么就替换掉

可以看到最后一个节点比两次dfs快出许多

原文地址:https://www.cnblogs.com/tclan126/p/8492746.html