P2387 [NOI2014] 魔法森林

简要题意:

给定一些无向边,每条边有 a a a b b b 两个权值,要求判断能否到达终点 n n n ,能到达,就输出路径的最小的值,定义路径的最小值为这条路径上 a a a b b b 的最大值之和。

这道题正解好像是LCT,但是我不会LCT啊。所以就只能用一些自己会的算法了。

首先我们先主观臆断一下,发现如果我们先将 路径按照 a a a 的值的大小去排个序,然后再挨个加边,我们就每次加边都更新一下。因为题干说了会出现重边和自环,按照我们的思路,我们要保证 b b b 尽可能小,出现重边很好处理,就保留小的即可,那么如果是出现了环呢?我们实际上是不需要环的,因此我们就需要考虑删去哪一条边呢?因为环中删掉一条边,连通性是不变的,那么就删去 b b b 值最大的边即可。那么总体的思路就是先将边按照 a a a 排序,然后从小到大拓展,连边的时候边的权值为 b b b , 然后跑一边 s p f a spfa spfa ,如果每次更新 b b b ,最后再和 a n s ans ans 求一下最小值即可。

聪明的你肯定会发现在处理环的时候,为什么能保证删掉最大的边一定能保证不会影响最优解,因为如果当前保留一个较大的可能会少加一个更大的。

那么这种思路的正确性如何保证 ???

我们将 b b b 最大的那条边删去,连通性不变,那么就将这个删去的边替换成环中的剩下的边,这样会使走这个删去的边的解变的更小,所以并不会影响最优解。

那么我们就可以开新的动态加边,然后跑 s p f a spfa spfa 了 , s p f a spfa spfa L C T LCT LCT 好写又好调而且还快。多是一件美事啊!!!

下面是代码,目前是洛谷最优解 (2023年6月5日10点03分)

#include <iostream>
#include <algorithm>
#include <queue>
#include <cstring>

using namespace std;

const int N = 50005, M = 100005, INF = 0x3f3f3f3f;

int n, m, d[N], hh, tt, q[N], ans = INF;
int head[N], idx;
bool vis[N];

struct Edge    //树的边(最小生成树的结构体)
{
    int u, v, a, b;
    bool operator<(const Edge &z) const
    {
        return a < z.a;
    }
} g[M];

struct E
{
    int next, v, w;
} e[M << 1];

inline void add(int u, int v, int w)
{
    e[++idx] = (E){head[u], v, w};
    head[u] = idx;
}

inline int read()
{
    int x = 0, f = 1;
    char ch = getchar();
    while(ch > '9' || ch < '0')
    {
        if(ch == '-')
            f = -1;
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9')
        x = x * 10 + ch - '0', ch = getchar();
    return x * f;
}

inline void write(int x)
{
    if(x < 0)
    {
        x = -x;
        putchar('-');
    }
    if(x > 9)
        write(x / 10);
    putchar(x % 10 + '0');
}

inline void spfa()
{
    while (hh != tt)
    {
        int u = q[hh++];
        vis[u] = false;
        if (hh == N)   //证明跑完了所有边
            hh = 0;
        for (int i = head[u]; i; i = e[i].next)
        {
            int v = e[i].v;
            if (max(e[i].w, d[u]) < d[v])
            {
                d[v] = max(e[i].w, d[u]);
                if (!vis[v])
                {
                    q[tt++] = v, vis[v] = true;
                    if (tt == N)
                        tt = 0;
                }
            }
        }
    }
}

int main()
{
    memset(d, 0x3f, sizeof (d));
    d[1] = 0;
    n = read(), m = read();

    for (register int i = 1; i <= m; i++)
    {
        g[i].u = read();
        g[i].v = read();
        g[i].a = read();
        g[i].b = read();
    }

    sort(g + 1, g + 1 + m);  //按照a的大小排序

    for (register int i = 1; i <= m; i++)
    {
        add(g[i].u, g[i].v, g[i].b);
        add(g[i].v, g[i].u, g[i].b);
        
        hh = 0, tt = 2;
        q[0] = g[i].u, q[1] = g[i].v;
        vis[q[0]] = vis[q[1]] = true;
        spfa();
        
        if (d[n] != INF)
            ans = min(ans, g[i].a + d[n]);
    }

    if (ans == INF)
        puts("-1");
    else
        write(ans);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值