还有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快出许多