PAT 甲级 1003 Emergency两种做法(spfa、dijkstra)

题目链接

题目大意

n n n 个点, m m m 条边的无向图,点有点权。
求从 C 1 C1 C1 C 2 C2 C2 的最短路径,最短路径的数量,以及最短路径中最大的点权和。保证 C 1 C1 C1 C 2 C2 C2 是连通的。
数据范围: n ≤ 500 n \leq 500 n500
猜测:各种数据不超过 int,没有重边。

方法一 spfa+dfs

思路

spfa求最短路以及最短路中的最大点权和
dfs求最短路的数量

错误点

开始时只用了 spfa,求最短路的数量时会有累计更新(第一次用 x x x 累加了 y y y,后面 x x x 如果发生更新,可能会再次累加 y y y ),使求出的数量多于实际数量。后增加了dfs,感觉时间复杂度有点大。所以最终使用了dijkstra.

code

/*
2020-7-5 11:23:34
nero

没说边权范围,是否路径和超过int,是否有负边权 
没说有没有重边 
*/
#include <bits/stdc++.h>

using namespace std;
#define lson(u) (u<<1)
#define rson(u) (u<<1|1)
#define Pi acos(-1)
#define iINF 0x3f3f3f3f
#define lINF 0x3f3f3f3f3f3f3f
#define EPS 0.000000001
#define pii pair<int,int>
typedef long long ll;
typedef unsigned long long ull;

const int MAXN = 505;  
const ll Mod = 1e9+7;
int n, m, C1, C2;
bool vis[MAXN];
int teams[MAXN],f[MAXN],cnt[MAXN],tms[MAXN];
vector<int>v[MAXN],w[MAXN];
queue<int>q;
void spfa(int s) {
	q.push(s);
	f[s] = 0;
	cnt[s] = 1;
	vis[s] = 1;
	tms[s] = teams[s];
	while(!q.empty()) {
		int x = q.front();
		q.pop();
		for(int i = 0; i < v[x].size(); i++) {
			int xx = v[x][i];
			int len = f[x]+w[x][i];
			if(len < f[xx]) {
		//		cout << "Yes" << endl;
				cnt[xx] = cnt[x];
				f[xx] = len;
				tms[xx] = tms[x] + teams[xx];
				if(!vis[xx]) {
					vis[xx] = 1;
					q.push(xx);
				}
			}
			else if(len == f[xx]) {
		//		cout << "No" << endl;
				cnt[xx]+=cnt[x];
				if(tms[xx] < tms[x] + teams[xx]) {
					tms[xx] = tms[x] + teams[xx];
					if(!vis[xx]) {
						vis[xx] = 1;
						q.push(xx);
					}					
				}
			}
		//	cout << x << "->" << xx << " f=" << f[xx] << " cnt=" << cnt[xx] << "  teams=" << tms[xx] << endl;
		}
		vis[x] = 0;
	}
	return;
}
int dfs_cnt = 0;
void dfs(int x, int path) {
	if(x == C2) {
		if(path == f[C2])
		dfs_cnt++;
		return;
	}
	if(path > f[C2]) return;
	for(int i = 0; i < v[x].size(); i++) {
		int xx = v[x][i];
		if(!vis[xx] && f[xx] == f[x] + w[x][i]) {
			vis[xx] = 1;
			dfs(xx, f[xx]);
			vis[xx] = 0;
		}	
	}
}
int main() {
	cin >> n >> m >> C1 >> C2;
	for(int i = 0; i < n; i++) {
		cin >> teams[i];
		f[i] = iINF;
	}	
	int x, y,xy;
	for(int i = 0; i < m; i++) {
		cin >> x >> y >> xy;
		v[x].push_back(y);
		w[x].push_back(xy);
		v[y].push_back(x);
		w[y].push_back(xy);
	}
	spfa(C1);
	memset(vis,0,sizeof(vis));
	vis[C1] = 1;
	dfs(C1,0); 
	cout << dfs_cnt << " " << tms[C2] << endl;
	return 0;
} 
/*
spfa求路径数量在这个样例中是错误的,可以验证
2 5 1 0
1 2
0 1 1
0 1 2
0 1 3
0 1 4
1 0 2
*/

方法二 dijkstra

思路

因为dijkstra每次用一个点更新其它点之后,这个点就不会再被用到,避免了多次更新造成的错误累加。

code

/*
2020-7-5 11:29:08
nero

没说边权范围,是否路径和超过int,是否有负边权 
没说有没有重边 
*/
#include <bits/stdc++.h>

using namespace std;
#define lson(u) (u<<1)
#define rson(u) (u<<1|1)
#define Pi acos(-1)
#define iINF 0x3f3f3f3f
#define lINF 0x3f3f3f3f3f3f3f
#define EPS 0.000000001
#define pii pair<int,int>
typedef long long ll;
typedef unsigned long long ull;

const int MAXN = 505;  
const ll Mod = 1e9+7;
int n, m, C1, C2;
bool vis[MAXN];
int teams[MAXN],dis[MAXN],cnt[MAXN],tms[MAXN];
vector<int>v[MAXN],w[MAXN];

void dijkstra(int s) {
	for(int i = 0; i < v[s].size(); i++) {
		dis[v[s][i]] = w[s][i];
	}
	dis[s] = 0;
	cnt[s] = 1;
	tms[s] = teams[s];	
	int Min, p;
	for(int i = 0; i < n; i++) {
		Min = iINF;
		for(int j = 0; j < n; j++) {
			if(!vis[j] && dis[j] < Min) {
				p = j;
				Min = dis[j];
			}
		}
		vis[p] = 1;
		for(int j = 0; j < v[p].size(); j++) {
			int xx = v[p][j];
			if(!vis[xx] && dis[xx] > dis[p] + w[p][j]) {
				dis[xx] = dis[p] + w[p][j];
				cnt[xx] = cnt[p];
				tms[xx] = tms[p] + teams[xx];
			}
			else if(!vis[xx] && dis[xx] == dis[p] + w[p][j]) {
				cnt[xx] += cnt[p];
				if(tms[xx] < tms[p] + teams[xx]) {
					tms[xx] = tms[p] + teams[xx];
				}
			}
		}
	}
	return;	
}


int main() {
	cin >> n >> m >> C1 >> C2;
	for(int i = 0; i < n; i++) {
		cin >> teams[i];
		dis[i] = iINF;
	}	
	int x, y,xy;
	for(int i = 0; i < m; i++) {
		cin >> x >> y >> xy;
		v[x].push_back(y);
		w[x].push_back(xy);
		v[y].push_back(x);
		w[y].push_back(xy);
	}
	dijkstra(C1);

	cout << cnt[C2]  << " " << tms[C2] << endl;
	return 0;
} 

都0202年了我还只会用spfa

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值