QTREE4 - Query on a tree IV【LCT】

题目链接


给定一棵 n 个点的带边权的树,点从 1 到 n 编号。每个点可能有两种颜色:黑或白。我们定义 dist(a,b) 为点 a 至点 b 路径上的权值之和。

一开始所有的点都是白色的。

要求作以下操作:

C a 将点 a 的颜色反转。(黑变白,白变黑)

A 询问 dist(a,b) 的最大值。a,b 点都必须为白色(a 与 b 可以相同),显然如果树上仍存在白点,查询得到的值一定是个非负数。

特别地,如果 A 操作时树上没有白点,输出 They have disappeared.


关于动态点分治/点分树写法。


  这里讲一下关于LCT的做法,确实不大好写,和不大好想。

  参考资料:https://www.luogu.com.cn/blog/Minamoto/solution-sp2666

前置知识

要知道基础的知识,LCT是多个Splay通过虚边相连的森林,每个Splay都是一条链。

 

假设并固定树的根

  我们假设树的根是1,于是可以把其他的所有的边的权值都放到点上去了,因为LCT是无法记录边的信息的,但是可以维护点权,我们假设一开始的每个Splay都是有且仅有自己本身这个点,将边的信息存到了点上去,保证了维护了边的信息。

 

Splay树与Splay树的传递

  树与树的信息之间要保证传递的话,肯定是要用一些数据结构来维护虚边的,因为对于Splay实际上还是一棵二叉树,所以最多维护两个结点的信息,而多出来的结点要通过堆来进行传递。

但是Splay与Splay树之间的信息呢?有时候的树可能是这样的:

  那么,如果是从4这个点到5这号点的边存在的话,应该如何去更新呢?

  我假设存这样的两个东西,lmx[x]表示以x为Splay的root的这棵Splay树的最浅的结点到最远的白点的距离,rmx[x]表示以x为Splay的root的这棵Splay树的最深的结点到最远的白点的距离,这样,当信息在两棵Splay之间传递的时候,就可以利用虚点的数据结构来维护了。

  • 在一棵Splay树上的结点的信息的传递
  1. lmx[x]可以是左子树的lmx[lc]直接继承
  2. lmx[x]也可以通过以x为根的Splay子树的最浅的结点到x点的距离:size[lc] + val[x] + w[x],这里的w[x]是x点的状态(是白点就为0,不然就是-INF说明不能将它作为白点的端点)
  3. lmx[x]还可以继续向x的右儿子延伸,先看实边,于是,因为深度的连续性关系(LCT的Splay必然是原树上的一条链),我们可以取lmx[rc]来保证了连续,因为rc的最浅一定就是x的直接相连的点,size[lc] + val[x] + lmx[rc]
  4. 对于虚边来更新lmx[x],虚边的Splay中的最浅的点一定是和x直接相连的,所以我们可以开一个堆,来维护最大值,存进所有虚边的lmx[x],最后只用去找虚边的最大的lmx[x]就可以了,所以假设这里用s[x]来表示这个堆(每个点开一个),于是就是size[lc] + val[x] + s[x].top()

  知晓了lmx[x]在Splay树上的信息的维护,接下去我们可以推一下rmx[x]的信息了,在细节上还是略有些不同的,区别就在于x,如果从更浅的点到x,那么必然会经过x的父亲到x的这条边,所以要加上val[x],但是走深度大于x的点,就不一定会经过x和x的父亲的这条边了,所以,在这里要细节的对待val[x]这条信息,我下面简单的写一下:

  1. rmx[rc]直接继承
  2. size[rc] + w[x]这里就不需要加“val[x]”了,是因为在x点就停下了
  3. size[rc] + val[x] + rmx[lc]从更浅的点,必然经过val[x]
  4. 去往其他深度深的点,以x为中间结点,也就是x作为深度最浅,从rc过来,去往另一个深度更深的方向,这里肯定会去往另外的虚边,因为深度更深,所以还是利用s[x]就可以了,size[rc] + s[x].top()​​​​​​​

 

记录答案

  上面讲了那么多信息的更新,却迟迟没有提到如何去记录答案。

  答案会是有几种可能?

  1. 对于一个Splay,可能是由它的左子树的最深结点的最远距离的白点到右子树的最浅结点所能到达的白点,此时要经过x和x的父亲的这条“val[x]”的边
  2. 也可能答案在左子树的Splay或者右子树的Splay中出现了,所以要去记录每棵子树的可能答案
  3. 也有可能答案存在于虚点之中,所以还要再开一个堆anss[x]来维护虚点的答案,用以继承
  4. 如果x是个白点的话,也有可能是x到虚点的Splay中的最浅点的最远白点的距离也就是s[x].top()

 

Access()函数

  LCT的精髓就在于此,因为有且只有它会是的虚边和实边进行互换。

  首先,维护的是上看提到的s[x]anss[x],分别是“虚Splay中的最浅的点所能到达的最远的白点的距离”、“虚点Splay的答案,用来做传递使用”。

  在改变实虚的时候,不要忘记删去原有的,然后再引进新的虚,虚实转换。

 

初始化

  这里要特别讲一下初始化,是为什么?是因为我们用1来假定根,使得信息维护从边转移到了结点上了,将边权信息化作点权信息。

  这里会产生虚边,于是就要做一个处理了,对于虚边,不要忘记把信息放进s[x]anss[x]中去。也不要忘记pushup()。

 

Code

#include <iostream>
#include <cstdio>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <limits>
#include <vector>
#include <stack>
#include <queue>
#include <set>
#include <map>
#include <bitset>
//#include <unordered_map>
//#include <unordered_set>
#define lowbit(x) ( x&(-x) )
#define pi 3.141592653589793
#define e 2.718281828459045
#define INF 0x3f3f3f3f
#define HalF (l + r)>>1
#define lsn rt<<1
#define rsn rt<<1|1
#define Lson lsn, l, mid
#define Rson rsn, mid+1, r
#define QL Lson, ql, qr
#define QR Rson, ql, qr
#define myself rt, l, r
using namespace std;
typedef unsigned long long ull;
typedef unsigned int uit;
typedef long long ll;
const int maxN = 1e5 + 7;
//const int maxN = 5;
int N, Q, white, c[maxN][2], r[maxN], fa[maxN], size[maxN], col[maxN], lmx[maxN], rmx[maxN], mxs[maxN], w[maxN], val[maxN], ans;
struct heap
{
    priority_queue<int> Que, Del;
    inline bool empty()
    {
        while(!Que.empty() && !Del.empty() && Que.top() == Del.top()) { Que.pop(); Del.pop(); }
        return Que.empty();
    }
    inline void push(int val) { Que.push(val); }
    inline void clear(int val) { Del.push(val); }
    inline int size() { return (int)(Que.size() - Del.size()); }
    inline int top()
    {
        while(!Que.empty() && !Del.empty() && Que.top() == Del.top()) { Que.pop(); Del.pop(); }
        return !Que.empty() ? Que.top() : -INF;
    }
    inline void pop()
    {
        while(!Que.empty() && !Del.empty() && Que.top() == Del.top()) { Que.pop(); Del.pop(); }
        Que.pop();
    }
    inline int sec()
    {
        if(!empty())
        {
            int tmp = top(), re = -INF; pop();
            if(!empty())
            {
                re = top();
            }
            push(tmp);
            return re;
        }
        else return -INF;
    }
} s[maxN], anss[maxN];
inline bool isroot(int x) { return c[fa[x]][0] != x && c[fa[x]][1] != x; }
inline void pushup(int x)
{
    if(!x) return;
    size[x] = size[c[x][0]] + size[c[x][1]] + val[x];
    lmx[x] = max(lmx[c[x][0]], size[c[x][0]] + max(w[x], max(s[x].top(), lmx[c[x][1]])) + val[x]);
    rmx[x] = max(rmx[c[x][1]], size[c[x][1]] + max(w[x], max(s[x].top(), rmx[c[x][0]] + val[x])));
    int L = max(max(w[x], s[x].top()), rmx[c[x][0]] + val[x]), R = max(max(w[x], s[x].top()), lmx[c[x][1]]) + val[x];
    mxs[x] = max(lmx[c[x][1]] + L, rmx[c[x][0]] + R);
    mxs[x] = max(mxs[x], max(mxs[c[x][0]], mxs[c[x][1]]));
    mxs[x] = max(mxs[x], anss[x].top());
    mxs[x] = max(mxs[x], s[x].top() + s[x].sec());
    if(!w[x]) mxs[x] = max(mxs[x], max(0, s[x].top()));
}
inline void pushr(int x)
{
    if(!x) return;
    swap(c[x][0], c[x][1]);
    r[x] ^= 1;
}
inline void pushdown(int x)
{
    if(!x) return;
    if(r[x])
    {
        pushr(c[x][0]);
        pushr(c[x][1]);
        r[x] = 0;
    }
}
void Rotate(int x)
{
    int y = fa[x], z = fa[y], k = c[y][1] == x, cop = c[x][k ^ 1];
    if(!isroot(y)) c[z][c[z][1] == y] = x;
    fa[x] = z;
    c[y][k] = cop;
    fa[cop] = y;
    c[x][k ^ 1] = y;
    fa[y] = x;
    pushup(y); pushup(x);
}
int Stap[maxN];
void Splay(int x)
{
    int y = x, z = 0;
    Stap[++z] = y;
    while(!isroot(y)) Stap[++z] = y = fa[y];
    while(z) pushdown(Stap[z--]);
    while(!isroot(x))
    {
        y = fa[x]; z = fa[y];
        if(!isroot(y)) (c[z][0] == y) ^ (c[y][0] == x) ? Rotate(x) : Rotate(y);
        Rotate(x);
    }
}
void access(int x)
{
    int y = 0;
    while(x)
    {
        Splay(x);
        if(c[x][1])
        {
            s[x].push(lmx[c[x][1]]);
            anss[x].push(mxs[c[x][1]]);
        }
        if(y)
        {
            s[x].clear(lmx[y]);
            anss[x].clear(mxs[y]);
        }
        c[x][1] = y;
        pushup(x);
        y = x; x = fa[x];
    }
}
int head[maxN], cnt;
struct Eddge
{
    int nex, to, val;
    Eddge(int a=-1, int b=0, int c=0):nex(a), to(b), val(c) {}
} edge[maxN << 1];
inline void addEddge(int u, int v, int w)
{
    edge[cnt] = Eddge(head[u], v, w);
    head[u] = cnt++;
}
inline void _add(int u, int v, int w) { addEddge(u, v, w); addEddge(v, u, w); }
void dfs(int u, int father)
{
    for(int i=head[u], v; ~i; i=edge[i].nex)
    {
        v = edge[i].to;
        if(v == father) continue;
        fa[v] = u; val[v] = edge[i].val;
        dfs(v, u);
        s[u].push(lmx[v]);
        anss[u].push(mxs[v]);
    }
    pushup(u);
}
void update(int x)
{
    access(x);
    Splay(x);
    col[x] ^= 1;
    w[x] = col[x] ? 0 : -INF;
    pushup(x);
    ans = mxs[x];
    if(col[x]) white++;
    else white--;
}
inline int query(int x = 1)
{
    access(x); Splay(x);
    return anss[x].top();
}
inline void init()
{
    white = N; lmx[0] = rmx[0] = mxs[0] = -INF;
    for(int i=1; i<=N; i++)
    {
        r[i] = 0; c[i][0] = c[i][1] = 0; head[i] = -1; col[i] = 1;  //all white begin
        size[i] = 1;
        w[i] = 0;
    }
}
int main()
{
    scanf("%d", &N);
    init();
    for(int i=1, u, v, w; i<N; i++)
    {
        scanf("%d%d%d", &u, &v, &w);
        _add(u, v, w);
    }
    val[1] = 0;
    dfs(1, 0);
    ans = mxs[1];
    scanf("%d", &Q);
    char op[3]; int x;
    while(Q--)
    {
        scanf("%s", op);
        if(op[0] == 'C')
        {
            scanf("%d", &x);
            update(x);
        }
        else
        {
            if(!white) printf("They have disappeared.\n");
            else printf("%d\n", ans);
        }
    }
    return 0;
}

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Wuliwuliii

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值