【NOI2014】 魔法森林---解题报告

传送门

题目大意

给定 \(n\) 个点和 \(m\) 条边。每条边包含起点终点和两个精灵的最低限制,求最少需要携带的精灵数量。

题目解析

直接套 LCT 板子

将所有边按照进行升序排序,从小到大将边加入,在已经加入边的图上找路径的最大值,求出最大值和当前枚举的和用于更新全局的最小值答案。

为什么呢?因为要 \(a\),\(b\) 都满足才能通过某条边,所以结果必定为某条边的 \(a_i\)\(b_i\), 因此可以固定 \(a\) 的信息来降低复杂度。即每次选取小于等于 \(a_i\) 大小的边去维护一条 \(1\)\(n\) 的路径.

动态加边,维护最大值。

直接套 LCT 板子!!

虽然题目给出的是一张图, 但实际上只需要维护出一条从 \(1\)\(n\) 的路径即可.因此当新加入一条边会使维护的树变成图时, 就需要去找到环, 若新边比环中最大值小, 那么将环中的最大边删去, 加入新边即可。

然后要注意用并查集(好像是卡常,因为 yxc 直接用的并查集

利用动态树的特性快速求路径上的最大点权值。最后注意一下删边时的编号映射。(关于我忘了切断子树卡了半天15pts艹)

代码实现

#include <bits/stdc++.h>

#define int long long
#define rint register int

#define endl '\n'

using namespace std;

const int N = 1e6 + 5;

int n, m, p[N], stk[N], ans = 0x3f3f3f3f;
struct Edge
{
    int x, y, a, b;
    bool operator<(const Edge &t) const { return a < t.a; }
} e[N];
struct node
{
    int s[2], p, v, mx, rev;
} tr[N];

int inline min(int a, int b)
{
    return a < b ? a : b;
}

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

void inline pushrev(int u)
{
    swap(tr[u].s[0], tr[u].s[1]);
    tr[u].rev ^= 1;
    return;
}

void inline pushup(int u)
{
    tr[u].mx = u;
    int ll = tr[tr[u].s[0]].mx;
    int rr = tr[tr[u].s[1]].mx;
    if (tr[ll].v > tr[tr[u].mx].v)
    {
        tr[u].mx = ll;
    }
    if (tr[rr].v > tr[tr[u].mx].v)
    {
        tr[u].mx = rr;
    }
    return;
}

void inline pushdown(int u)
{
    if (tr[u].rev)
    {
        pushrev(tr[u].s[0]);
        pushrev(tr[u].s[1]);
        tr[u].rev = 0;
    }
    return;
}

bool inline isroot(int u)
{
    return tr[tr[u].p].s[0] != u && tr[tr[u].p].s[1] != u;
}

void inline rotate(int x)
{
    int y = tr[x].p;
    int z = tr[y].p;
    int k = tr[y].s[1] == x;
    if (!isroot(y))
    {
        tr[z].s[tr[z].s[1] == y] = x;
    }
    tr[x].p = z;
    tr[y].s[k] = tr[x].s[k ^ 1], tr[tr[x].s[k ^ 1]].p = y;
    tr[x].s[k ^ 1] = y, tr[y].p = x;
    pushup(y);
    pushup(x);
    return;
}

void inline splay(int x)
{
    int top = 0, r = x;
    stk[++top] = r;
    while (!isroot(r))
    {
        stk[++top] = r = tr[r].p;
    }
    while (top)
    {
        pushdown(stk[top--]);
    }
    while (!isroot(x))
    {
        int y = tr[x].p, z = tr[y].p;
        if (!isroot(y))
        {
            if ((tr[z].s[1] == y) ^ (tr[y].s[1] == x))
                rotate(x);
            else
                rotate(y);
        }
        rotate(x);
    }

    return;
}

void inline access(int x)
{
    int z = x;
    for (rint y = 0; x; y = x, x = tr[y].p)
    {
        splay(x);
        tr[x].s[1] = y, pushup(x);
    }
    splay(z);
    return;
}

void inline makeroot(int x)
{
    access(x);
    pushrev(x);
    return;
}

int inline findroot(int x)
{
    access(x);
    while (tr[x].s[0])
    {
        pushdown(x);
        x = tr[x].s[0];
    }
    splay(x);
    return x;
}

void inline split(int x, int y)
{
    makeroot(x);
    access(y);
    return;
}

void inline link(int x, int y)
{
    makeroot(x);
    if (findroot(y) != x)
        tr[x].p = y;
    return;
}

void inline cut(int x, int y)
{
    makeroot(x);
    if (findroot(y) == x && tr[x].s[1] == y && !tr[y].s[0])
    {
        tr[y].p = tr[x].s[1] = 0;
        pushup(x);
    }
    return;
}

signed main()
{
    cin >> n >> m;

    for (rint i = 1; i <= m; i++)
    {
        int x, y, a, b;
        cin >> x >> y >> a >> b;
        e[i] = {x, y, a, b};
    }

    sort(e + 1, e + 1 + m);

    for (rint i = 1; i <= n + m; i++)
    {
        p[i] = i;
        if (i > n)
            tr[i].v = e[i - n].b;
        tr[i].mx = i;
    }

    for (rint i = 1; i <= m; i++)
    {
        int x = e[i].x;
        int y = e[i].y;
        int a = e[i].a;
        int b = e[i].b;

        if (find(x) == find(y))
        {
            split(x, y);
            int t = tr[y].mx;
            if (tr[t].v > b)
            {
                cut(t, e[t - n].x);
                cut(t, e[t - n].y);
                link(i + n, x);
                link(i + n, y);
            }
        }
        else
        {
            p[find(x)] = find(y);
            link(i + n, x);
            link(i + n, y);
        }
        if (find(1) == find(n))
        {
            split(1, n);
            ans = min(ans, a + tr[tr[n].mx].v);
        }
    }

    if (ans != 0x3f3f3f3f)
    {
        cout << ans << endl;
        return 0;
    }

    puts("-1");

    return 0;
}
posted @ 2022-07-16 17:27  PassName  阅读(63)  评论(0编辑  收藏  举报