给定一个无向图,其中每条边表示成 ( u , v , c ) (u,v,c) (u,v,c),其中 c c c这条边的权重,给定起点和终点。求一条从 s s s到 t t t的路径使得路径上的最大权重减去最小权重尽可能小。
输入格式
第一行两个数
n
n
n和
m
m
m,表示图中的顶点个数和边的个数,顶点的编号从1到n。
接下来 m m m行,每行三个数为图中的一条边,其中,满足 1 ≤ u i , v i ≤ n , 1 ≤ c i ≤ 2 ∗ 1 0 9 1 \leq u_i,v_i \leq n, 1 \leq c_i \leq 2*10^9 1≤ui,vi≤n,1≤ci≤2∗109。
接下来输入 s , t s,t s,t。
输出格式
一个数表示答案,如果从s到t没有路径,输出-1。
样例
input
4 4
1 2 2
2 3 4
1 4 1
3 4 2
1 3
output
1
提示
使用枚举+并查集的算法,将路径理解成判断u和v是否连通,枚举是指枚举子图中权重最小的边。
思路及代码
思路:按边存储,然后将边排序。以不同的边为起点遍历,找出最值之差。
#include <algorithm>
#include <iostream>
using namespace std;
struct edge {
int from, to;
int cost;
bool operator<(const edge &t) const { return cost < t.cost; }
} e[5001];
int f1[5001], f2[5001];
int n, m, st, ed;
void init(int n, int *a) {
for (int i = 1; i <= n; i++)
a[i] = i;
}
int getf(int v, int *a) {
if (a[v] == v)
return v;
return a[v] = getf(a[v], a);
}
void merge(int a, int b, int *c) {
int t1 = getf(a, c);
int t2 = getf(b, c);
if (t1 != t2) {
c[t2] = t1;
}
}
int main() {
cin >> n >> m;
init(n, f1);
for (int i = 0; i < m; i++) {
cin >> e[i].from >> e[i].to >> e[i].cost;
if (getf(e[i].from, f1) != getf(e[i].to, f1)) {
merge(e[i].from, e[i].to, f1);
}
}
cin >> st >> ed;
if (getf(st, f1) != getf(ed, f1)) {
cout << -1 << endl;
return 0;
}
sort(e, e + m);
int res = INT32_MAX;
for (int i = 0; i < m; i++) {
init(n, f2);
int Min = 0x3f3f3f3f, Max = -1;
for (int j = i; j < m; j++) {
//排除不在联通集之内的边
if (getf(e[j].from, f1) != getf(st, f1) && getf(e[j].to, f1) != getf(st, f1))
continue;
//更新最值
Min = min(Min, e[j].cost);
Max = max(Max, e[j].cost);
merge(e[j].from, e[j].to, f2);
//如果成功联通,则更新答案
if (getf(st, f2) == getf(ed, f2)) {
res = min(res, Max - Min);
break;
}
}
}
cout << res << endl;
}
法二:暴力搜索
直接遍历从s到t的每条路径并得出答案。
遍历路径的方法:
假设我们要找出结点3到结点6的所有路径,那么,我们就设结点3为起点,结点6为终点。我们需要的存储结构有:一个保存路径的栈、一个保存已标记结点的数组,那么找到结点3到结点6的所有路径步骤如下:
(1)我们建立一个存储结点的栈结构,将起点3 入栈,将结点3 标记为入栈状态;
(2)从结点3 出发, 找到结点3 的第一个非入栈状态的邻结点1,将结点1 标记为入栈状态;
(3)从结点1 出发, 找到结点1 的第一个非入栈状态的邻结点0,将结点0 标记为入栈状态;
(4)从结点0 出发, 找到结点0 的第一个非入栈状态的邻结点2,将结点2 标记为入栈状态;
(5)从结点2 出发, 找到结点2 的第一个非入栈状态的邻结点5,将结点5 标记为入栈状态;
(6)从结点5 出发, 找到结点5 的第一个非入栈状态的邻结点6,将结点6 标记为入栈状态;
(7)栈顶结点6 是终点, 那么, 我们就找到了一条起点到终点的路径,输出这条路径;
(8)从栈顶弹出结点6,将6 标记为非入栈状态;
(9)现在栈顶结点为5, 结点5 没有除终点外的非入栈状态的结点,所以从栈顶将结点5 弹出;
(10)现在栈顶结点为2,结点2 除了刚出栈的结点5 之外,还有非入栈状态的结点6,那么我们将结点6 入栈;
(11)现在栈顶为结点6,即找到了第二条路径,输出整个栈,即为第二条路径;
(12)重复步骤2-11,就可以找到从起点3 到终点6 的所有路径;
(13)栈为空,算法结束。
#include <iostream>
#include <stack>
#include <vector>
using namespace std;
int edge[5001][5001];
vector<pair<int, int>> map[5001];
stack<int> s;
int f[5001], book[5001];
int n, m, a, b, w, st, ed;
void init(int n) {
for (int i = 1; i <= n; i++)
f[i] = i;
}
int getf(int v) {
if (f[v] == v)
return v;
return f[v] = getf(f[v]);
}
void merge(int a, int b) {
int t1 = getf(a);
int t2 = getf(b);
if (t1 != t2) {
f[t2] = t1;
}
}
int max_delete_min(stack<int> tmp) {
int Min = 0x3f3f3f3f, Max = -1;
int pre = tmp.top();
tmp.pop();
while (tmp.size()) {
Min = min(edge[tmp.top()][pre], Min);
Max = max(edge[tmp.top()][pre], Max);
pre = tmp.top();
tmp.pop();
}
return Max - Min;
}
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
if (i == j)
edge[i][j] = 0;
else
edge[i][j] = 0x3f3f3f3f;
}
}
init(n);
for (int i = 0; i < m; i++) {
cin >> a >> b >> w;
edge[a][b] = w;
edge[b][a] = w;
//后续遍历时,入栈顶点的边不能重复使用,所以要引入标记
map[a].push_back(make_pair(b, 0));
map[b].push_back(make_pair(a, 0));
if (getf(a) != getf(b)) {
merge(a, b);
}
}
cin >> st >> ed;
if (getf(st) != getf(ed)) {
cout << -1 << endl;
return 0;
}
s.push(st);
book[st] = 1;
int res = 0x3f3f3f3f;
while (s.size()) {
int tmp = s.top();
for (auto &i : map[tmp]) {
if (!i.second) {
i.second = 1;
if (!book[i.first]) {
s.push(i.first);
book[i.first] = 1;
if (i.first == ed) {
res = min(res, max_delete_min(s));
book[i.first] = 0;
for (auto &j : map[s.top()]) {
j.second = 0;
}
s.pop();
continue;
}
break;
}
}
}
int flag = 0;
for (auto j : map[s.top()]) {
if (!j.second) {
flag = 1;
break;
}
}
if (!flag) {
for (auto &j : map[s.top()]) {
j.second = 0;
}
book[s.top()] = 0;
s.pop();
}
}
cout << res << endl;
}