1003 Emergency(Dijkstra,Bellman-Ford,SPFA三种解法)

Posted CSU迦叶

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了1003 Emergency(Dijkstra,Bellman-Ford,SPFA三种解法)相关的知识,希望对你有一定的参考价值。

目录

1. Dijkstra解法

2. Bellman-Ford解法

3. SPFA解法

4. Dijkstra解法AC代码

5. Bellman-Ford解法AC代码

6. SPFA解法非满分(19/25)代码

1. Dijkstra解法

这题不仅涉及到基础的解法,还涉及到第二标准(累计军队数量),以及还要记录最短路径条数。这些都是在绷紧路径的if语句中修改,我认为自己在这里的嵌套判断写法(先判断该结点是否已被访问过再进行下一步)比书上的复合条件句要清楚

//绷紧路径
for(int j=0;j<city[minC].nbr.size();j++){
	int nbr = city[minC].nbr[j].no;
	if(vis[nbr]==0){
		if(d[minC]+city[minC].nbr[j].dis<d[nbr]){
			n[nbr] = n[minC];
			d[nbr] = d[minC]+city[minC].nbr[j].dis;
			t[nbr] = t[minC] + city[nbr].teamN;
		}else if(d[minC]+city[minC].nbr[j].dis==d[nbr]){
			n[nbr] += n[minC];
			if(t[minC] + city[nbr].teamN > t[nbr])t[nbr] = t[minC] + city[nbr].teamN;
		}
	}
}	

对于起点的初始化,有三个条件就要初始化三个

//将起点的距离设置为0 
d[sCity] = 0;
//将起点的最短路径条数设置为1
n[sCity] = 1;
//将起点的军队数目设置为自身所有
t[sCity] = city[sCity].teamN;

值得注意的是最短路径条数的更新,如果有优化方案,当前结点的最短路径数目应该赋值为前驱结点的最短路径数目而不是1,如果有同样优的方案,当前结点的最短路径数目应该增加前驱结点的最短路径数目而不是加1

2. Bellman-Ford解法

BF算法可以判断有没有可达负环,但是在这题的背景下用不到。它主要分为紧绷部分和判断负环部分。同样棘手的是最短路径的条数怎么办,当遇到可以紧绷时,直接用前驱的路径条数更新后继节点的,这个和Dijkstra一样,但是当遇到同优策略时,就不一样了,起初我也不会,看了参考书明白。需要设置一个前趋结点表set<int> pre[maxn],如果遇到同优路径,先把当前前驱结点加入,然后把当前结点的最短路径数清零(非常容易忘),对前驱结点集合进行遍历,所有前驱结点的最短路径条数之和就是当前结点的最短路径条数。此外,遇到更优路径的时候,需要先把前驱结点集合清空,再插入当前前驱结点。

if(vis[no]==0){
	if(d[j]+dis<d[no]){
		n[no] = n[j];
		t[no] = t[j]+city[no].teamN;
		d[no] = d[j]+dis;
		pre[no].clear();
		pre[no].insert(j);  
	}else if(d[j]+dis==d[no]){
		pre[no].insert(j);
		n[no] = 0; 
		for(set<int>::iterator it = pre[no].begin();it!=pre[no].end();it++){
			n[no] += n[*it];
		}
		if(t[no]<t[j]+city[no].teamN)t[no] = t[j]+city[no].teamN;
	}
}

3. SPFA解法

这里用的是BFS版本的SPFA,利用的规律是“d[u]变,只有u的后继节点的最短路径才有可能改变”,所以在进行一次优化以后,才把当前优化过的结点放进队列中。至于数目的更新,由于这其实是改进的BF算法,和BF是一样的。但是我不知道为什么有两个测试点答案错误。以后有机会再改写成DFS试试吧。

while(!Q.empty()){
		int now = Q.front();
		Q.pop();
		inq[now] = 0;//它现在不在队中了
		for(int i=0;i<city[now].nbr.size();i++){
			int no = city[now].nbr[i].no;
			int dis  = city[now].nbr[i].dis;
			if(d[now]+dis<d[no]){
				d[no] = d[now]+dis;
				t[no] = t[now]+city[no].teamN;
				n[no] = n[now];
				pre[no].clear();
				pre[no].insert(now);
				if(inq[no]==0){
					Q.push(no);
					inq[no] = 1;
					inqN[no]++;
					if(inqN[no]>=cityN)return false;	
				}
			}else if(d[now]+dis==d[no]){
				if(t[no] < t[now]+city[no].teamN)t[no] = t[now]+city[no].teamN;
				pre[no].insert(now);
				n[no] = 0;
				for(set<int>::iterator it = pre[no].begin();it!=pre[no].end();it++){
					n[no] += n[*it];
				}
			} 
		} 
	}

4. Dijkstra解法AC代码

#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<bits/stdc++.h>
using namespace std;

const int INF = 1000000000;//10的9次方 
const int maxn = 510;
const double eps = 1e-3;

struct Node{
	int no;
	int dis;
	Node(int _no,int _dis):no(_no),dis(_dis){}
	
};

struct City{
	vector<Node> nbr;
	int teamN;
}city[maxn];

int cityN,roadN,sCity,dCity;

bool vis[maxn] = {0};
int d[maxn]; 
int t[maxn] = {0};//数组t存放的是最短路径上累积的最大救援队数
int n[maxn] = {0};//n存放最短路径的数量

void Dijkstra(){
	//填充最短距离路径数组(其他三个都初始化过了)
	fill(d,d+cityN,INF);
	//将起点的距离设置为0 
	d[sCity] = 0;
	//将起点的最短路径条数设置为1
	n[sCity] = 1;
	//将起点的军队数目设置为自身所有
	t[sCity] = city[sCity].teamN;	
	
	for(int i=0;i<cityN;i++){//每一次访问一个结点 
		//找到距离当前数组距离最小的数组
		int minD = INF,minC  = -1;//最短距离和对应城市编号
		for(int j=0;j<cityN;j++){
			if(d[j]<minD&&vis[j]==0){
				minD = d[j];
				minC = j;
			} 
		} 
		 
		if(minC==-1)return;//说明所有结点都加入了S 
		else vis[minC] = 1;
		 
		//绷紧路径
		for(int j=0;j<city[minC].nbr.size();j++){
			int nbr = city[minC].nbr[j].no;
			if(vis[nbr]==0){
				if(d[minC]+city[minC].nbr[j].dis<d[nbr]){
					n[nbr] = n[minC];
					d[nbr] = d[minC]+city[minC].nbr[j].dis;
					t[nbr] = t[minC] + city[nbr].teamN;
				}else if(d[minC]+city[minC].nbr[j].dis==d[nbr]){
					n[nbr] += n[minC];
					if(t[minC] + city[nbr].teamN > t[nbr])t[nbr] = t[minC] + city[nbr].teamN;
				}
			}
		}		 
	}	
}


int main(){
	
	scanf("%d %d %d %d",&cityN,&roadN,&sCity,&dCity);
	
	//读入每个城市救援队的数量 
	for(int i=0;i<cityN;i++){
		scanf("%d",&city[i].teamN);
	}
	//读入每一条路的长度
	for(int i=0;i<roadN;i++){
		int c1,c2,len;
		scanf("%d %d %d",&c1,&c2,&len);
		Node* n1 = new Node(c1,len);
		city[c2].nbr.push_back(*n1);
		Node* n2 = new Node(c2,len);
		city[c1].nbr.push_back(*n2);
	}
	
	//调用Dijkstra算法
	Dijkstra(); 
	
	printf("%d %d\\n",n[dCity],t[dCity]); 
	
	return 0; 
}

5. Bellman-Ford解法AC代码

#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<bits/stdc++.h>
using namespace std;

const int INF = 1000000000;//10的9次方 
const int maxn = 510;
const double eps = 1e-3;

struct Node{
	int no;
	int dis;
	Node(int _no,int _dis):no(_no),dis(_dis){}
	
};

struct City{
	vector<Node> nbr;
	int teamN;
}city[maxn];

int cityN,roadN,sCity,dCity;

bool vis[maxn] = {0};
int d[maxn]; 
int t[maxn] = {0};//数组t存放的是最短路径上累积的最大救援队数
int n[maxn] = {0};//n存放最短路径的数量

set<int> pre[maxn];//存放每个结点的前驱结点,BF独有,用于更新最短路径条数 

bool BF(){
	fill(d,d+cityN,INF);
	d[sCity] = 0;
	n[sCity] = 1;
	t[sCity] = city[sCity].teamN;
	
	//进行紧绷操作
	for(int i=0;i<cityN-1;i++){
		for(int j=0;j<cityN;j++){
			for(int k=0;k<city[j].nbr.size();k++){
				int no = city[j].nbr[k].no;
				int dis = city[j].nbr[k].dis;
				if(vis[no]==0){
					if(d[j]+dis<d[no]){
						n[no] = n[j];
						t[no] = t[j]+city[no].teamN;
						d[no] = d[j]+dis;
						pre[no].clear();
						pre[no].insert(j);  
					}else if(d[j]+dis==d[no]){
						pre[no].insert(j);
						n[no] = 0; 
						for(set<int>::iterator it = pre[no].begin();it!=pre[no].end();it++){
							n[no] += n[*it];
						}
						if(t[no]<t[j]+city[no].teamN)t[no] = t[j]+city[no].teamN;
					}
				}
			}
		}
	}
	
	//判断有无可达负环
	for(int i=0;i<cityN;i++){
		for(int j=0;j<city[i].nbr.size();j++){
			int no = city[i].nbr[j].no;
			int dis = city[i].nbr[j].dis;
			if(d[no]>d[i]+dis)return false;
		}
	}
	
	return true;
	 
}


int main(){
	
	scanf("%d %d %d %d",&cityN,&roadN,&sCity,&dCity);
	
	//读入每个城市救援队的数量 
	for(int i=0;i<cityN;i++){
		scanf("%d",&city[i].teamN);
	}
	//读入每一条路的长度
	for(int i=0;i<roadN;i++){
		int c1,c2,len;
		scanf("%d %d %d",&c1,&c2,&len);
		Node* n1 = new Node(c1,len);
		city[c2].nbr.push_back(*n1);
		Node* n2 = new Node(c2,len);
		city[c1].nbr.push_back(*n2);
	}
	
	//调用BF算法
	int res = BF();
	
	if(res)printf("%d %d\\n",n[dCity],t[dCity]); 
	
	return 0; 
}

6. SPFA解法非满分(19/25)代码

#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<bits/stdc++.h>
using namespace std;

const int INF = 1000000000;//10的9次方 
const int maxn = 510;
const double eps = 1e-3;

struct Node{
	int no;
	int dis;
	Node(int _no,int _dis):no(_no),dis(_dis){}
	
};

struct City{
	vector<Node> nbr;
	int teamN;
}city[maxn];

int cityN,roadN,sCity,dCity;

bool vis[maxn] = {0};
int d[maxn]; 
int t[maxn] = {0};//数组t存放的是最短路径上累积的最大救援队数
int n[maxn] = {0};//n存放最短路径的数量

set<int> pre[maxn];//存放每个结点的前驱结点,BF独有,用于更新最短路径条数 

bool inq[maxn] = {0};
int inqN[maxn] = {0};//将进入队列的次数 

bool SPFA(){
	fill(d,d+cityN,INF);
	d[sCity] = 0;
	n[sCity] = 1;
	t[sCity] = city[sCity].teamN;
	
	queue<int> Q;
	Q.push(sCity);
	inq[sCity] = 1;
	inqN[sCity]++;
	
	while(!Q.empty()){
		int now = Q.front();
		Q.pop();
		inq[now] = 0;//它现在不在队中了
		for(int i=0;i<city[now].nbr.size();i++){
			int no = city[now].nbr[i].no;
			int dis  = city[now].nbr[i].dis;
			if(d[now]+dis<d[no]){
				d[no] = d[now]+dis;
				t[no] = t[now]+city[no].teamN;
				n[no] = n[now];
				pre[no].clear();
				pre[no].insert(now);
				if(inq[no]==0){
					Q.push(no);
					inq[no] = 1;
					inqN[no]++;
					if(inqN[no]>=cityN)return false;	
				}
			}else if(d[now]+dis==d[no]){
				if(t[no] < t[now]+city[no].teamN)t[no] = t[now]+city[no].teamN;
				pre[no].insert(now);
				n[no] = 0;
				for(set<int>::iterator it = pre[no].begin();it!=pre[no].end();it++){
					n[no] += n[*it];
				}
			} 
		} 
	}
	
	return true;
}


int main(){
	
	scanf("%d %d %d %d",&cityN,&roadN,&sCity,&dCity);
	
	//读入每个城市救援队的数量 
	for(int i=0;i<cityN;i++){
		scanf("%d",&city[i].teamN);
	}
	//读入每一条路的长度
	for(int i=0;i<roadN;i++){
		int c1,c2,len;
		scanf("%d %d %d",&c1,&c2,&len);
		Node* n1 = new Node(c1,len);
		city[c2].nbr.push_back(*n1);
		Node* n2 = new Node(c2,len);
		city[c1].nbr.push_back(*n2);
	}
	
	//调用BF算法
	int res = SPFA();
	
	if(res)printf("%d %d\\n",n[dCity],t[dCity]); 
	
	return 0; 
}

以上是关于1003 Emergency(Dijkstra,Bellman-Ford,SPFA三种解法)的主要内容,如果未能解决你的问题,请参考以下文章

1003 Emergency(Dijkstra,Bellman-Ford,SPFA三种解法)

1003. Emergency

1003 Emergency

PAT 1003. Emergency

甲级1003 Emergency

1003 Emergency (25 分)