洛谷 P1967 [NOIP2013 提高组] 货车运输

货车运输

题目描述

A 国有 n n n 座城市,编号从 1 1 1 n n n,城市之间有 m m m 条双向道路。每一条道路对车辆都有重量限制,简称限重。

现在有 q q q 辆货车在运输货物, 司机们想知道每辆车在不超过车辆限重的情况下,最多能运多重的货物。

输入格式

第一行有两个用一个空格隔开的整数 $ n,m$,表示 A 国有 $ n$ 座城市和 m m m 条道路。

接下来 m m m 行每行三个整数 x , y , z x, y, z x,y,z,每两个整数之间用一个空格隔开,表示从 $x $ 号城市到 $ y $ 号城市有一条限重为 z z z 的道路。
注意: x ≠ y x \neq y x=y,两座城市之间可能有多条道路 。

接下来一行有一个整数 q q q,表示有 q q q 辆货车需要运货。

接下来 q q q 行,每行两个整数 x , y x,y x,y,之间用一个空格隔开,表示一辆货车需要从 x x x 城市运输货物到 y y y 城市,保证 x ≠ y x \neq y x=y

输出格式

共有 q q q 行,每行一个整数,表示对于每一辆货车,它的最大载重是多少。
如果货车不能到达目的地,输出 − 1 -1 1

样例

样例输入
4 3
1 2 4
2 3 3
3 1 1
3
1 3
1 4
1 3
样例输出
3
-1
3

提示

对于 30 % 30\% 30% 的数据, 1 ≤ n < 1000 1 \le n < 1000 1n<1000 1 ≤ m < 10 , 000 1 \le m < 10,000 1m<10,000 1 ≤ q < 1000 1\le q< 1000 1q<1000

对于 60 % 60\% 60% 的数据, 1 ≤ n < 1000 1 \le n < 1000 1n<1000 1 ≤ m < 5 × 1 0 4 1 \le m < 5\times 10^4 1m<5×104 1 ≤ q < 1000 1 \le q< 1000 1q<1000

对于 100 % 100\% 100% 的数据, 1 ≤ n < 1 0 4 1 \le n < 10^4 1n<104 1 ≤ m < 5 × 1 0 4 1 \le m < 5\times 10^4 1m<5×104,$1 \le q< 3\times 10^4 $, 0 ≤ z ≤ 1 0 5 0 \le z \le 10^5 0z105

笨蒟蒻的个人题解

传送门
这题是一道非常经典的题目,当然本题解法不止一种,起码有三四种,所以这个题非常经典。但并不是说题解方法很多就很简单,相反,这题不简单了。

在这里插入图片描述

题意见图,比如说从 1 1 1 号点走到 3 3 3​ 号点,它有两种走法:

​ 1、 1 − > 2 − > 3 1 -> 2 -> 3 1>2>3

​ 2、 1 − > 3 1 -> 3 1>3

那么这种第一种走法负载量是 3 3 3,第二种走法负载量是 1 1 1,所以我选第一种。

ok,这就是这道题的含义。

怎么思考?

显然这个图有一个边是没有用处的,那就是那个边长为 1 1 1 的那个,因为从 1 1 1 3 3 3 已经可以联通了,并且是最大的,所以那个边长为 1 1 1 的就不需要了,无论如何都不会经过 1 1 1 那个边,既然没用,就可以删掉,那么一直删一直删,直到不能删为止,你会惊奇的发现这成了一颗树,为什么呢?因为两点之间最多只有一条路径了,而那个路径恰恰就是你要求的路径的最小值!那么这个时候自然而然地就可以想到,这个时候就需要找最大生成树!然后去重构!这是第一个要想到的突破点!找最大生成树有两种方法,一种是 prim,一种是 Kruskal,这道题不能用 prim,因为开不了这么大的数组,而且在时间上还会爆(堆优化的另说,没试过)!所以得要用 Kruskal,那么这个算法复杂度是 O ( m l o g m ) O(mlogm) O(mlogm)。通过这个算法进行树的重构,把有环的问题变成树的问题!

重构树这是第一步要想到的,然后接着想,怎么去想求在一个无根树上的一条线上的最小值呢?

在这里插入图片描述

就以这个图为例子吧,比如说 4 4 4 6 6 6 吧,它的沿线是: 4 − > 2 − > 1 − > 5 − > 6 4 -> 2 -> 1 -> 5 -> 6 4>2>1>5>6。很容易看出来最小值是 3 3 3,对吧。当然你可以用 dfs/bfs 一个结点一个结点去搜索,这样的话我们来分析一下复杂度,时间复杂度是 O ( m q ) O(mq) O(mq),显然超时了,所以不可以,要再去优化!

how?

我们可以这样,既然这个是一个无根树,对吧,那么我们可以先认一个结点做根节点,然后做 lca,找最近公共祖先,那么答案就是两个结点到最近公共祖先的值的最小值,做 lca 的同时还要去维护一下路径最小值(我新开了个数组 ww,这里和倍增跳的思路一样),这里还是推荐用倍增法,tarjan 离线确实是不太好写。还有维护深度和最小值的时候最好用 dfs 吧,因为它不止一颗树,还是不建议用 bfs(因为它可能有些结点没有,我是 wa 了才改成 dfs 的)。

ok,那这个题就做完了,献上笨蒟蒻写了两个小时的代码(主要是码量有点多,太难调了,总出问题)。

(2023 年 5 月 12 日写的,今天 7 月 3 日回顾一下)。。。

AC_Code

#include<iostream>
#include<algorithm>
#include<string>
#include<string.h>
#include<vector>
#include<cmath>
#include<map>
using namespace std;
#define int long long
#define fi first
#define se second
#define inf 0x3f3f3f3f
typedef pair<int, int>PII;
const int N = 5e4 + 10, M = N * 2;

int n, m, Q;
struct Edge
{
    int u, v, w;
    bool operator<(const Edge& t) const
    {
        return t.w < w;
    }
}edge[N];
int p[N], depth[N], fa[N][18], ww[N][18];
int e[M], ne[M], w[M], h[N], idx;
bool st[N];

int find(int x)
{
    if(p[x] != x) p[x] = find(p[x]);
    return p[x];
}

void add(int a, int b, int c)
{
    e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}

void Kruskal()
{
    sort(edge, edge + m);
    for(int i = 0; i < N; i++) p[i] = i;

    for(int i = 0; i < m; i++)
    {
        int u = edge[i].u, v = edge[i].v, z = edge[i].w;
//        cout << '-' <<  z << endl;
        if(find(u) != find(v))
        {
            p[find(u)] = find(v);
            add(u, v, z), add(v, u, z);
        }
    }
}
/*
void bfs(int root)
{
    memset(dep, inf, sizeof(dep));
    dep[0] = 0, dep[root] = 1;
    int hh = 0, tt = 0;
    q[0] = root;
    while(hh <= tt)
    {
        int t = q[hh++];
        for(int i = h[t]; ~i; i = ne[i])
        {
            int j = e[i];
            if(dep[j] > dep[t] + 1)
            {
                dep[j] = dep[t] + 1;
                q[++tt] = j;
                fa[j][0] = t;
                //if(j == 3) cout << w[i] << endl;
                ww[j][0] = w[i];
            }
        }
    }
}
*/
void dfs(int u, int f)
{
    st[u] = true;
    for(int i = h[u]; ~i; i = ne[i])
    {
        int j = e[i];
        if(j == f) continue;
        if(st[j]) continue;
        depth[j] = depth[u] + 1;
        fa[j][0] = u;
        ww[j][0] = w[i];
        dfs(j, u);
    }
}

void lca_init()
{
    for(int k = 1; k < 18; k++)
    {
        for(int j = 1; j <= n; j++)
        {
            fa[j][k] = fa[fa[j][k - 1]][k - 1];
            ww[j][k] = min(ww[j][k - 1], ww[fa[j][k - 1]][k - 1]);
            //cout << "j = " << j << " k = " << k << " " <<ww[j][k] << endl;
        }
    }
}

int lca(int a, int b)
{
    if(find(a) != find(b)) return -1;
    if(depth[a] < depth[b]) return lca(b, a);
//    cout << a << " " << b << endl << endl;

    int res = inf;
    for(int k = 17; k >= 0; k--)
        if(depth[fa[a][k]] >= depth[b])
        {
//            cout << "a = " << a << " k = " << k << " " << ww[a][k] << endl;
            res = min(res, ww[a][k]);
            a = fa[a][k];
        }

    if(a == b) return res;
    for(int k = 17; k >= 0; k--)
    {
        if(fa[a][k] != fa[b][k])
        {
            res = min({res, ww[a][k], ww[b][k]});
            a = fa[a][k];
            b = fa[b][k];
        }
    }

    res = min({res, ww[a][0], ww[b][0]});
    return res;
}

void solve()
{
    cin >> n >> m;
    memset(h, -1, sizeof(h));
    //memset(ww, inf, sizeof(ww));
    for(int i = 0; i < m; i++)
    {
        int a, b, c;
        cin >> a >> b >> c;
        edge[i] = {a, b, c};
    }

    Kruskal();

    for(int i = 1; i <= n; i++)
        if(!st[i])
        {
//            depth[i] = 1;
            dfs(i, -1);
            fa[i][0] = i;
            ww[i][0] = inf;
        }
    //bfs(1);
    lca_init();

    cin >> Q;
    while(Q--)
    {
        int a, b;
        cin >> a >> b;
        cout << lca(a, b) << endl;
    }
}

signed main()
{
    int T = 1;
//    cin >> T;
    while(T--)
        solve();
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值