[LuoguP3348] [ZJOI2016]大森林

洛谷传送门

题目描述

小Y家里有一个大森林,里面有n棵树,编号从1到n。一开始这些树都只是树苗,只有一个节点,标号为1。这些树都有一个特殊的节点,我们称之为生长节点,这些节点有生长出子节点的能力。

小Y掌握了一种魔法,能让第l棵树到第r棵树的生长节点长出一个子节点。同时她还能修改第l棵树到第r棵树的生长节点。她告诉了你她使用魔法的记录,你能不能管理她家的森林,并且回答她的询问呢?

输入输出格式

输入格式:

第一行包含 2 个正整数 n,m,共有 n 棵树和 m 个操作。接下来 m 行,每行包含若干非负整数表示一个操作,操作格式为:0 l r 表示将第 l 棵树到第 r 棵树的生长节点下面长出一个子节点,子节点的标号为上一个 0 号操作叶子标号加 1(例如,第一个 0 号操作产生的子节点标号为 2), l 到 r 之间的树长出的节点标号都相同。保证 1<=l<=r<=n 。1 l r x 表示将第 l 棵树到第 r 棵树的生长节点改到标号为 x 的节点。对于 i (l<=i<=r)这棵树,如果标号 x的点不在其中,那么这个操作对该树不产生影响。保证 1<=l<=r<=n , x 不超过当前所有树中节点最大的标号。2 x u v 询问第 x 棵树中节点 u 到节点 v 点的距离,也就是在第 x 棵树中从节点 u 和节点 v 的最短路上边的数量。保证1<=x<=n,这棵树中节点 u 和节点 v 存在。N<=10^5,M<=2*10^5

输出格式:

输出包括若干行,按顺序对于每个小Y的询问输出答案

输入输出样例

输入样例#1:
5 5
0 1 5
1 2 4 2
0 1 4
2 1 1 3
2 2 1 3
输出样例#1:
1
2

解题分析

一道 Dark D a r k ♂LCT题。
我们可以发现,由于后生长的节点在查询操作时不会对之前的节点的距离产生影响,并且由于保证查询合法,树上多长的点也不会对查询产生影响, 我们完全可以将所有查询在建完所有树后再处理。

因为区间更换生长节点不方便打标记, 那么我们考虑离线所有的操作, 逐棵树处理所有的操作和询问。这时我们发现:似乎每个区间操作可以分为两个操作:在 l l 节点加入一棵子树(可将所有长在其上的节点视为一棵子树), 在r+1的时候将这棵子树割掉即可。

现在问题在于我们如何快速操作所有子树, 在复杂度可接受的情况下进行link和cut操作。

%%%会ETT的dalao, 蒟蒻只会虚点的操作… 因为我们需要查询LCA,而这一切都是在splay上面进行的, 我们可以维护子树的大小用来找LCA。

我们可以看做每一次更改后生长出来的节点都长在一个虚拟节点上, 这样我们可以将每棵子树视为一个附件, 用虚拟节点方便地动态link和cut。对于每个0操作, 在最新的虚点上长出一个实点。 对于每个1操作, 申请一个新的虚点, 并连接上一个虚节点。

这样做有什么好处?因为显然一次改生长节点会牵连到其后加上的所有0操作(直到下一个1操作), 当到达某个1操作的左边界时直接将虚拟节点和其父节点断开并连到相应的实节点下, 到达该1操作的右边界时再断开其与实点的连接,并连回原虚节点。仔细想一想就会发现,在原来的虚节点下面即意为默认连在最近一次改为生长节点的位置下, 又因为所有虚点的贡献为0, 所以不影响答案统计。

那么我们怎么求两点间距离吗?直接大力 split s p l i t ?当然不行。如果 split s p l i t 则默认换根,而我们维护的树是有根的,显然会导致树的结构改变。 所以我们维护子树大小, 在splay后即得到其到根节点的距离。而在我们splay后最后一个访问的节点即为两点之间LCA, 最后容斥一下即可。

代码如下:

#include <cstdio>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <algorithm>
#include <cctype>
#define R register
#define IN inline
#define W while
#define gc getchar()
#define MX 505000
#define ls tree[now].son[0]
#define rs tree[now].son[1]
#define dad tree[now].fat
#define BD 100000
#define MQ 200005
template <class T>
IN void in(T &x)
{
    x = 0; R char c = gc;
    W (!isdigit(c)) c = gc;
    W (isdigit(c))
    x = (x << 1) + (x << 3) + c - 48, c = gc;
}
namespace Off_Line
{
    int dot, q, aux, tq, cur, arr, tot;
    struct Event
    {int pos, from, to;}eve[MQ];
    IN bool operator < (const Event &x, const Event &y)
    {return x.pos < y.pos;}
    struct Query
    {
        int rt, a, b, id;
    }que[MQ];
    IN bool operator < (const Query &x, const Query &y)
    {return x.rt < y.rt;}
    int lb[MX], rb[MX], vir[MX], ans[MQ];
}
namespace LCT
{
    struct Node
    {
        int son[2], fat, val, sum;
    }tree[MX];
    IN bool nroot(const int &now) {return tree[dad].son[1] == now || tree[dad].son[0] == now;}
    IN bool get(const int &now) {return tree[dad].son[1] == now;}
    IN void pushup(const int &now)
    {
        tree[now].val = tree[now].sum = (now <= BD);
        if(ls) tree[now].sum += tree[ls].sum;
        if(rs) tree[now].sum += tree[rs].sum;
    }
    IN void rotate(const int &now)
    {
        R bool dir = get(now);
        R int fa = dad, grand = tree[fa].fat;
        tree[fa].son[dir] = tree[now].son[dir ^ 1];
        tree[tree[now].son[dir ^ 1]].fat = fa;
        if(nroot(fa)) tree[grand].son[get(fa)] = now;
        tree[now].fat = grand;
        tree[now].son[dir ^ 1] = fa;
        tree[fa].fat = now;
        pushup(fa);
    }
    IN void splay(const int &now)
    {
        R int fa, grand;
        W (nroot(now))
        {
            fa = dad, grand = tree[fa].fat;
            if(nroot(fa)) rotate(get(fa) == get(now) ? fa : now);
            rotate(now);
        }
        pushup(now);
    }
    IN int access(R int now)
    {
        R int x = 0;
        for (; now; x = now, now = dad)
            splay(now), rs = x, pushup(now);
        return x;
    }
    IN void link(const int &x, const int &y)
    {splay(x), tree[x].fat = y;}
    IN void cut(const int &now)
    {
        access(now), splay(now);
        ls = tree[ls].fat = 0;
        pushup(now);
    }
}
using namespace LCT;
using namespace Off_Line;
int main(void)
{
    int a, b, c, d;
    in(dot); in(q);
    aux = cur = BD + 1; arr = 1;
    lb[1] = 1, rb[1] = dot;
    tree[1].sum = tree[1].val = 1;
    link(aux, 1);
    for (R int i = 1; i <= q; ++i)
    {
        in(a), in(b), in(c);
        switch (a)
        {
            case 0://建立虚点
            {
                arr++;
                vir[arr] = aux;
                tree[arr].val = tree[arr].sum = 1;
                link(arr, aux);
                lb[arr] = b, rb[arr] = c;
                break;
            }
            case 1://连接实点
            {
                in(d); b = std::max(b, lb[d]); c = std::min(c, rb[d]);
                if(b > c) continue;//因为每个点只有某些区间拥有, 提前去重。
                aux++;
                link(aux, aux - 1);
                eve[++tot] = {b, aux, d};
                eve[++tot] = {c + 1, aux, aux - 1};
                break;
            }
            case 2: {in(d); tq++; que[tq] = {b, c, d, tq};}
        }
    }
    std::sort(eve + 1, eve + 1 + tot);
    std::sort(que + 1, que + 1 + tq);
    cur = 1; int deal = 1, lca;
    R int rt;
    for (R int i = 1; i <= dot && cur <= tot && deal <= tq; ++i)
    {
        W(eve[cur].pos <= i & cur <= tot)
        {
            cut(eve[cur].from);
            link(eve[cur].from, eve[cur].to);
            ++cur;
        }//处理该树及之前的所有操作
        W (que[deal].rt <= i && deal <= tq)
        {
            rt = 0;
            access(que[deal].a), splay(que[deal].a); rt += tree[que[deal].a].sum;
            lca = access(que[deal].b), splay(que[deal].b); rt += tree[que[deal].b].sum;
            access(lca), splay(lca), ans[que[deal].id] = rt - (tree[lca].sum << 1);
            ++deal;
        }
    }
    for (R int i = 1; i <= tq; ++i)
        printf("%d\n", ans[i]);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值