【洛谷P4193】变化的道路——LCT+线段树

前置技能点:LCT,线段树

如果你不知道上面的东西,请先行了解

start_of_题面

传送门

end_of_题面

正式写题解之前,先吐槽一句,为什么这题在洛谷上标签会是普及-啊!!!直接写了两个小时+调了不知道多长时间。不过看讨论好像之前的标签是入门,大佬们还真是调皮(汗)。

第一眼看到这题发现是动态维护最小生成树,支持加边删边,立刻想到LCT,因为前段时间刚刚做了NOI2014魔法森林。然后想了想觉得可以 O(N) O ( N ) 扫一遍,利用一个类似ZJOI2016大森林的做法怎么搞一搞。于是就开始敲,敲到一半发现不对,有很大问题。因为这道题是动态维护最小生成树,所以不知道在每次操作的左右区间都该干什么。我们每次加边可以很轻松维护最小生成树,但是删边就没法做了,因为我们不知道删了这条边应该重新加进去哪一条边。

于是就想换方法,想了想可持久化LCT,发现我简直在做梦,然后突然想起之前听tty大神讲课的时候讲到的一个方法——时间线段树。

我们对时间建一颗线段树,然后把出现过的边按照存在的时间区间挂在线段树上,比如样例

这里写图片描述

图中的三元组 (u,v,w) ( u , v , w ) 代表一条端点为 u u v且边权为 w w 的边。

由于样例过大,这里只展示前六天的情况

一开始的边一直存在,所以挂在区间[1,rmost]上,中间出现的边会挂在下面的区间,有可能是整个的,也有可能被分在线段树的多个节点,比如图中的 (1,2,1) ( 1 , 2 , 1 ) 就因为出现时间是 [1,2] [ 1 , 2 ] 所以被放在一个点上,而 (2,3,8) ( 2 , 3 , 8 ) 因为出现的时间是 [2,3] [ 2 , 3 ] 所以被分在了两个点上。

这样的话,我们从这颗线段树的根走到一个叶子节点,就对应了一天内的情况,这天内所有存在的边都会在我们路上经过的线段树的节点内存着,我们只需要在这个过程中用LCT动态地维护最小生成树,然后到了线段树的叶子节点就输出答案就可以了。

出于时间和空间的考虑,我们在每一个线段树的节点上开一个 vector v e c t o r 保存边,另外为了不浪费时间,我们可以在线段树的每个节点动态维护最小生成树的同时用另一个 vector v e c t o r 记下我们做过的操作,离开这个节点的时候,我们倒序处理这些操作,是断开边的就连回去,是连边的就断开,这样回到它的父亲节点时可以直接继续访问父亲节点的另一个儿子。

粗略计算一下,一个区间在线段树上最多被分为 logN l o g N 个区间,也就是说在这个过程中每条输入的边最多被加入删除 logN l o g N 次,线段树的节点数级别是 O(N) O ( N ) ,LCT的复杂度是均摊 O(logN) O ( l o g N ) ,题目给了7s时间,应该可过。

实测在洛谷开O2的情况下12个点跑了25320ms。

start_of_code

#include <map>
#include <cmath>
#include <vector>
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;

#define LL long long

const int rmost = 32766;
const int N = 350000 + 5000;

int n, m, l, r;
struct Edge{
    int x, y, tp;
    LL w;
}us[N];
struct Opt{
    int fc, x, y;
}ff[N], pus;

inline LL read()
{
    LL a = 0;
    char ch;
    LL f = 1;
    while(!((((ch = getchar()) >= '0') && (ch <= '9')) || (ch == '-')));
    if(ch == '-')
        f = -1;
    else
    {
        a = a * 10;
        a += ch - '0';
    }
    while((((ch = getchar()) >= '0') && (ch <= '9')) || (ch == '-'))
    {
        a = a * 10;
        a += ch - '0';
    }
    return a * f;
}

namespace LCT{
    int son[N][2], big[N], fa[N], rev[N];
    LL val[N];
    inline int which(int x)
    {
        return x == son[fa[x]][1];
    }

    inline bool isroot(int x)
    {
        return x != son[fa[x]][1] && x != son[fa[x]][0];
    }

    inline void update(int x)
    {
        big[x] = x;
        if(son[x][1] && val[big[son[x][1]]] > val[big[x]])
            big[x] = big[son[x][1]];
        if(son[x][0] && val[big[son[x][0]]] > val[big[x]])
            big[x] = big[son[x][0]];
    }

    inline void pushdown(int x)
    {
        if(rev[x])
        {
            rev[son[x][1]] ^= 1;
            rev[son[x][0]] ^= 1;
            swap(son[x][1], son[x][0]);
            rev[x] = 0;
        }
    }

    inline void rotate(int x)
    {
        int f = fa[x], g = fa[f], d = which(x);
        if(!isroot(f))
            son[g][which(f)] = x;
        fa[x] = g;
        son[f][d] = son[x][d ^ 1];
        fa[son[f][d]] = f;
        son[x][d ^ 1] = f;
        fa[f] = x;
        update(f);
        update(x);
    }

    int stk[N], top = 0;

    inline void splay(int x)
    {
        top = 0;
        stk[++top] = x;
        for(int i = x;!isroot(i);i = fa[i])
            stk[++top] = fa[i];
        for(int i = top;i >= 1;--i)
            pushdown(stk[i]);
        for(;!isroot(x);rotate(x))
            if(!isroot(fa[x]))
                rotate(which(x) == which(fa[x]) ? fa[x] : x);
        update(x);
    }

    inline void access(int x)
    {
        int y = 0;
        for(;x;y = x, x = fa[x])
            splay(x), son[x][1] = y, update(x);
    }

    inline void makeroot(int x)
    {
        access(x);
        splay(x);
        rev[x] ^= 1;
    }

    inline int findroot(int x)
    {
        access(x);
        splay(x);
        while(son[x][0])
            x = son[x][0];
        return x;
    }

    inline void link(int u, int v)
    {
        makeroot(u);
        fa[u] = v;
        splay(u);
    }

    inline void cut(int x, int y)
    {
        makeroot(x);
        access(y);
        splay(y);
        fa[x] = son[y][0] = 0;
        update(y);
    }

    inline int query(int u, int v)
    {
        makeroot(u);
        access(v);
        splay(v);
        return big[v];
    }
}
using namespace LCT;

inline void debug()
{
    for(int i = 1;i < 2 * n + m;++i)
    {
        cout << fa[i] << " " << val[i];
        if(i > n)
            cout << " " << us[i - n].x << " " << us[i - n].y;
        cout << endl;
    }
}

namespace Seg{
    vector<Edge> g[N];
    vector<Opt> op[N];
    int lc[N], rc[N];

    inline void build(int p, int l, int r)
    {
        g[p].clear();
        lc[p] = l, rc[p] = r;
        if(l == r)
            return;
        int mid = (l + r) >> 1;
        build(p << 1, l, mid);
        build(p << 1 | 1, mid + 1, r);
    }

    inline void add(int p, int l, int r, Edge in)
    {
        if(l == lc[p] && r == rc[p])
        {
            g[p].push_back(in);
            return;
        }
        int mid = (lc[p] + rc[p]) >> 1;
        if(r <= mid)
            add(p << 1, l, r, in);
        else if(l > mid)
            add(p << 1 | 1, l, r, in);
        else
            add(p << 1, l, mid, in), add(p << 1 | 1, mid + 1, r, in);
    }

    inline void solve(int p, LL ans)
    {
        LL down = ans;
        int s = g[p].size();
        for(int i = 0;i < s;++i)
        {
            Edge o = g[p][i];
            int u = o.x, v = o.y;
            bool f = 1;
            if(findroot(u) == findroot(v))
            {
                int I = query(u, v);
                if(val[I] > o.w)
                {
                    down -= val[I];
                    cut(us[I - n].x, I);
                    cut(I, us[I - n].y);
                    pus.fc = 1, pus.x = us[I - n].x, pus.y = I;
                    op[p].push_back(pus);
                    pus.fc = 1, pus.x = I, pus.y = us[I - n].y;
                    op[p].push_back(pus);
                }
                else
                    f = 0;
            }
            if(f)
            {
                link(u, o.tp);
                link(o.tp, v);
                pus.fc = 0, pus.x = u, pus.y = o.tp;
                op[p].push_back(pus);
                pus.fc = 0, pus.x = o.tp, pus.y = v;
                op[p].push_back(pus);
                down += o.w;
            }
        }
        if(lc[p] != rc[p])
        {
            solve(p << 1, down);
            solve(p << 1 | 1, down);
        }
        else
            printf("%lld\n", down + 1);
        if(p == 1)
            return;
        s = op[p].size();
        for(int i = s - 1;i >= 0;--i)
        {
            Opt o = op[p][i];
            if(o.fc == 1)
                link(o.x, o.y);
            else
                cut(o.x, o.y);
        }
    }
}
using namespace Seg;

int main()
{
    build(1, 1, rmost);
    n = read();
    for(int i = 1;i < n;++i)
    {
        us[i].x = read(), us[i].y = read(), us[i].w = read(), us[i].tp = n + i;
        val[us[i].tp] = us[i].w;
        add(1, 1, rmost, us[i]);
    }
    m = read();
    for(int i = n;i < n + m;++i)
    {
        us[i].x = read(), us[i].y = read(), us[i].w = read(), us[i].tp = 2 * n - 1 + (i - n + 1);
        l = read(), r = read();
        val[us[i].tp] = us[i].w;
        add(1, l, r, us[i]);
    }
    solve(1, 0);
    return 0;
}

end_of_code

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值