最短路非模板题 入门已过进阶未满(AcWing 341 && bzoj 2200 && poj 1734 && AcWing 349)

AcWing 341. 最优贸易 

题目:https://www.acwing.com/problem/content/343/

题目意思就是找到一条从1到n的路中的最大权值减去最小权值最大

建图,使用in[i]数组表示从1~i的路中的最小权值

建反向图,在反向图上用out[i]数组表示从n~i的路中的最大权值

最后遍历所有点,找出最大的out[i]-in[i]就是答案

题目:

#include<bits/stdc++.h>
using namespace std;
const int maxn1 = 1e5 + 7;
const int maxn2 = 1e6 + 7;
const int INF = 0x3f3f3f3f;
int in[maxn1], out[maxn1], num[maxn1];
int Head1[maxn1], To1[maxn2], Nxt1[maxn2]; //链式向前星建图
int Head2[maxn1], To2[maxn1], Nxt2[maxn2];
int tot1, tot2;
int n, m;
void add_edge(int fro, int to) { 
	Nxt1[++tot1] = Head1[fro]; //正向图
	To1[tot1] = to;
	Head1[fro] = tot1;
	Nxt2[++tot2] = Head2[to]; //反向图
	To2[tot2] = fro;
	Head2[to] = tot2;
}
typedef pair<int, int> P; //边,点
priority_queue<P, vector<P>, greater<P>>Q;
void Dijstra1() { //跑正向图
	memset(in, INF, sizeof(in));
	while (!Q.empty()) Q.pop();
	in[1] = num[1];
	Q.push(P(in[1], 1));
	while (!Q.empty()) {
		P p = Q.top(); Q.pop();
		int val = p.first, poi = p.second;
		if (in[poi] < val) continue;
		for (int i = Head1[poi]; i; i = Nxt1[i]) {
			int to = To1[i];
			if (in[to] > in[poi]) {
				in[to] = min(in[poi], num[to]);
				Q.push(P(in[to], to));
			}
		}
	}
}
void Dijstra2() { //跑反向图
	memset(out, 0, sizeof(out));
	out[n] = num[n];
	while (!Q.empty()) Q.pop();
	Q.push(P(out[n], n));
	while (!Q.empty()) {
		P p = Q.top(); Q.pop();
		if (out[p.second] > p.first) continue;
		for (int i = Head2[p.second]; i; i = Nxt2[i]) {
			int to = To2[i];
			if (out[to] < out[p.second]) {
				out[to] = max(num[to], out[p.second]);
				Q.push(P(out[to], to));
			}
		}
	}
}
int main() {
	scanf("%d %d", &n, &m);
	for (int i = 1; i <= n; i++) {
		scanf("%d",  num + i);
	}
	int fro, to, opt;
	for (int i = 1; i <= m; i++) {
		scanf("%d %d %d", &fro, &to, &opt);
		add_edge(fro, to);
		if (opt == 2) add_edge(to, fro);
	}
	Dijstra1();
	Dijstra2();
	int maxx = 0;
	for (int i = 1; i <= n; i++) {
		maxx = max(maxx, out[i] - in[i]);
	}
	printf("%d\n", maxx);
}

 

bzoj 2200:   https://www.lydsy.com/JudgeOnline/problem.php?id=2200

因为这道题有负边的存在,所以不可以用迪杰斯特拉求最短路,而用SPFA的话时间复杂度最差为O(nm),此题假如特殊构造图形则会卡时间,事实上(听说)就会卡SPFA

因为这道题一部分是双向路,一部分是单向路,只有单向路才有负数,可以利用这点解题。

题目还有另外的条件,假如有从a到b的单向路的话,就必然没有从b到a的路,也就是说将a到b的路切开之后跟a,b之间就断开了,所有跟a联通的点和所有跟b联通的点都会分开。

所以可以缩点,也就是说先构建一张只有双向边的图,那么此图一定是分割成不同块的图,将同一块的所有点压缩成一个点,之后再加入单向边。

然后根据欧拉序(每次从入度为0的大点里),在每个大点里跑迪杰斯特拉,这样就没有负边了

#include<bits/stdc++.h>
using namespace std;
const int maxn = 3e4;
const int INF = 0x3f3f3f3f;
struct edge {
	int to, val;
};
vector<edge> plan[maxn], road[maxn]; //plan 海路  road 陆路
int f[maxn], belong[maxn], dis[maxn], deg[maxn]; 
//f 并查集的父亲节点,缩点用, belong[i] : i属于哪个大点   dis:距离数组   deg:入度数组
int vis[maxn]; 
int T, R, P, S;
int find(int x) { //并查集
	return f[x] == x ? x : f[x] = find(f[x]);
}
void combine() { //缩点
	for (int i = 1; i <= T; i++) f[i] = i;
	for (int i = 1; i <= T; i++) {
		for (int j = 0; j < road[i].size(); j++) {
			int x = find(i), y = find(road[i][j].to);
			if (x != y) f[x] = y;
		}
	}
}
void get_deg() { //获得每个大点的入度
	queue<int> Q;
	memset(vis, 0, sizeof(vis));
	Q.push(S);
	vis[S] = 1;
	while (!Q.empty()) {
		int now = Q.front();	Q.pop();
		for (int i = 0; i < road[now].size(); i++) {
			int &to = road[now][i].to;
			if (!vis[to]) {
				vis[to] = 1;
				Q.push(to);
			}
		}
		for (int i = 0; i < plan[now].size(); i++) {
			int &to = plan[now][i].to;
			++deg[find(to)];
			if (!vis[to]) {
				vis[to] = 1;
				Q.push(to);
			}
		}
	}
}
queue<int> dq;
vector<int> go[maxn]; //每个大点中跑迪杰斯特拉时的起点数组,这几个点时跑dijstra
typedef pair<int, int> PP; //pair放点和边, pp.first: 边  pp.second: 点
void Dijstra(int in) { //in放的一定是这个点的id(大点的id)
	priority_queue<PP, vector<PP>, greater<PP> > Q;
	for (int i = 0; i < go[in].size(); i++) {
		Q.push(PP(dis[go[in][i]], go[in][i]));
	}
	while (!Q.empty()) {
		PP p = Q.top();	Q.pop();
		int now = p.second;
		if (vis[now]) continue; 
		vis[now] = 1;
		for (int i = 0; i < road[now].size(); i++) {
			int to = road[now][i].to, val = road[now][i].val;
			if (dis[to] > dis[now] + val) {
				dis[to] = dis[now] + val;
				Q.push(PP(dis[to], to));
			}
		}
		for (int i = 0; i < plan[now].size(); i++) {
			int to = plan[now][i].to, val = plan[now][i].val;
			int belong = find(to);
			if (dis[to] > dis[now] + val) {
				dis[to] = dis[now] + val;
				go[belong].push_back(to);
			}
			deg[belong]--;
			if (deg[belong]==0) dq.push(belong);//没入度就可以了
		}
	}
}
void top_solve() {
	dq.push(find(S));
	go[find(S)].push_back(S);
	memset(vis, 0, sizeof(vis));
	while (!dq.empty()) {
		int now = dq.front();
		dq.pop();
		Dijstra(now);
	}
}
int main() {
	cin >> T >> R >> P >> S;
	memset(dis, INF, sizeof(dis));
	dis[S] = 0;
	for (int i = 1; i <= R; i++) {
		int fro, to, val;
		scanf("%d %d %d", &fro, &to, &val);
		edge e;
		e.to = to, e.val = val;
		road[fro].push_back(e);
		e.to = fro;
		road[to].push_back(e);
	}
	combine(); //缩点
	for (int i = 1; i <= P; i++) {
		int fro, to, val;
		scanf("%d %d %d", &fro, &to, &val);
		edge e;
		e.to = to, e.val = val;
		plan[fro].push_back(e);
	}
	get_deg();//计算入度
	top_solve();
	for (int i = 1; i <= T; i++) {
		if (dis[i] == INF) printf("NO PATH\n");
		else printf("%d\n", dis[i]);
	}
	return 0;
}

 

poj 1734 Sightseeing trip

原题:http://poj.org/problem?id=1734

用ori[i][j]存放原始读入的路径长度,用ans保存最小权值的环

使用弗洛伊德的最外层循环k的时候,dis[i][j]表示经过不超过编号为k-1的点从i~j的最短路(i,j也不超过k-1)

对于第k个点,则让ans 和 dis[i][j] + ori[i][k] + ori[k][j] 比较  (将i,j看为回路的一条边,再加上ik以及kj这两条边构成环)

然后再对dis进行第k层的弗洛伊德转化(具体可以看代码)

然后还有一个是记录路径,可以使用一个二维数组bet[i][j]记录弗洛伊德转化的时候作为i,j中继点的k的值

每一次ans更新,都需要从头更新一次路径

注意:代码有一个坑点:

就是假如初始化dis数组为INF,那么在比较dis[i][j] + ori[i][k] + ori[k][j]的时候可能会爆long long

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#include<map>
#define ll long long 
#define ull unsigned long long 
using namespace std;
const int INF = 0x3f3f3f3f;
const int maxn = 307;
int ori[maxn][maxn], dis[maxn][maxn];
//ori:origin,记录原始道路长 dis:记录弗洛伊德之后的距离
int path[maxn], bet[maxn][maxn];
// path:记录最短路径  bet:between 记录i,j之间用于更新他们最短距离的中继点
int ind = 0;
void get_path(int i,int j) { //dfs更新路径
	if (bet[i][j] == 0) return;
	get_path(i, bet[i][j]);
	path[++ind] = bet[i][j];
	get_path(bet[i][j], j);
}
int main() {
	int n, m;
	cin >> n >> m;
	memset(dis, INF, sizeof(dis));
	memset(ori, INF, sizeof(ori));
	while (m--) {
		int fro, to, val;
		scanf("%d %d %d", &fro, &to, &val);
		dis[fro][to] = dis[to][fro] = val;
		ori[fro][to] = ori[to][fro] = val;
	}
	int ans = INF;
	for (int k = 1; k <= n; k++) {
		for (int i = 1; i < k; i++) {
			for (int j = i + 1; j < k; j++) {
				if ((long long)dis[i][j] + ori[k][j] + ori[i][k] < ans ) { // 这里 不等式前面有可能会爆int
					ans = dis[i][j] + ori[k][j] + ori[i][k];
					ind = 0; //每一次更新答案都需要重置path数组
					path[++ind] = i;
					get_path(i, j);
					path[++ind] = j;
					path[++ind] = k;
				}
			}
		}
		for (int i = 1; i <= n; i++) {
			for (int j = 1; j <= n; j++) {
				if (dis[i][j] > dis[i][k] + dis[k][j]) { //弗洛伊德更新
					dis[i][j] = dis[i][k] + dis[k][j];
					bet[i][j] = k;
				}
			}
		}
	}
	if (ans == INF) cout << "No solution." << endl;
	else {
		for (int i = 1; i <= ind; i++) printf("%d ", path[i]);
		printf("\n");
	}
	return 0;
}

 

AcWing 349 黑暗城堡

原题:https://www.acwing.com/problem/content/351/

首先S[i]=D[i] 就表示需要跑一次单源最短路

当方案不同的时候,但是仍然需要S[i]=D[i],其中D[i]是不会变的,因此S[i]不变。

因此需要有dis[j]=dis[i]+edge[i,j] 成立,才能保证s[i]不变。

(我觉得代码很清楚了)

那么dis[j]=dis[i]+edge[i,j]成立的时候,点i更换其连接的边,会不会对其他点有影响呢?

先看代码

#include<bits/stdc++.h>
using namespace std;
const int maxn = 5e5 + 7;
const int INF = 0x3f3f3f3f;
int Head[maxn], Nxt[maxn << 1], Val[maxn << 1], To[maxn << 1];
int n, m, tot;
void add_edge(int fro, int to, int val) {
	Nxt[++tot] = Head[fro];
	To[tot] = to;
	Val[tot] = val;
	Head[fro] = tot;
}
int dis[1007], vis[1007];
typedef pair<int, int> P;
priority_queue < P, vector<P>, greater<P> > Q;
void Dijstra() {
	memset(dis, INF, sizeof(dis));
	dis[1] = 0;
	Q.push(P(0, 1));
	while (!Q.empty()) {
		P p = Q.top();
		Q.pop();
		if (vis[p.second]) continue;
		int now = p.second;
		for (int i = Head[now]; i; i = Nxt[i]) {
			int &to = To[i], &val = Val[i];
			if (dis[to] > dis[now] + val) {
				dis[to] = dis[now] + val;
				Q.push(P(dis[to], to));
			}
		}
	}
}
#define ll long long
ll ans = 1;
ll mod = (1 << 31) - 1;
ll sto[1007];
int main() {
	cin >> n >> m;
	for (int i = 1; i <= m; i++) {
		int fro, to, val;
		scanf("%d %d %d", &fro, &to, &val);
		add_edge(fro, to, val);
		add_edge(to, fro, val);
	}
	Dijstra();
	memset(vis, 0, sizeof(vis));
	memset(sto, 0, sizeof(sto));
	for (int i = 1; i <= n; i++) {
		for (int j = Head[i]; j; j = Nxt[j]) {
			int &to = To[j], &val = Val[j];
			if (dis[to] == dis[i] + val) {
				sto[to]++;
			}
		}
	}
	for (int i = 2; i <= n; i++) {
		ans *= sto[i];
		ans %= mod;
	}
	cout << ans << endl;
}

比如原图是这个

对于这个图,可以找到一棵树

当寻找到d这个点的时候,dis[b]=dis[d]+edge[b,d]

是不是变成:

那E就被影响了

当然不是,其实应该是这样的:

切断的应该是通往B的,而不是通往D的边

仅仅通过这个例子来说明这样做的正确性

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值