路径差

给定一个无向图,其中每条边表示成 ( 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 1ui,vin,1ci2109

接下来输入 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;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值