洛谷P2486 lct做法

这篇博客介绍了一种利用LCT(Link Cut Tree)数据结构解决无根树上路径染色和查询颜色段数量的问题。LCT允许动态地增删边并维护树上路径信息。文章详细解释了LCT的内部操作,如旋转、反转、颜色更新等,并展示了如何在push_up过程中维护路径上的颜色段计数。此外,还给出了实现这些操作的C++代码片段,包括处理查询和修改操作的split、link、splay等函数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意:

给出一颗nnn个结点的无根树,处理mmm个操作,每个操作只会是如下两种之一:

(1)将结点aaabbb的路径上的所有点染成颜色kkk

(2)询问结点aaa到结点bbb的简单路径的颜色段数量

LCT方法:
LCTLCTLCT是用来维护动态增删边和树上路径的数据结构
对于每个splaysplaysplay,维护的是一条链,节点xxx在原树连接的是他在splaysplaysplay的前驱,不是splaysplaysplay的儿子,xxx的左子树是他之前的链,右子树是之后的链

对于路径(x,y)(x,y)(x,y),先split(x,y)split(x,y)split(x,y)

我们先考虑查询,sumsumsum保存当前splaysplaysplay的“连接处”的个数,即相邻颜色不一样的节点对数,答案即tree[y].sum+1tree[y].sum+1tree[y].sum+1,那么问题就到了如何在push_uppush\_uppush_up中维护这个值,显然,对节点xxxtree[x].sum=tree[son[0]].sum+tree[son[1]].sum+xtree[x].sum=tree[son[0]].sum+tree[son[1]].sum+xtree[x].sum=tree[son[0]].sum+tree[son[1]].sum+x节点对他前后两段形成的“连接处个数”,一种方法是找前驱,即用传统splaysplaysplay的方法,但在LCTLCTLCT内由于pre()pre()pre()会调用splay()splay()splay()splay()splay()splay()会调用$push_up()
,,push_up会调用会调用会调用pre(),循环递归,会出现错误。另一种方法是新增成员,来保存当前,循环递归,会出现错误。另一种方法是新增成员,来保存当前,循环递归,会出现错误。另一种方法是新增成员,来保存当前splay子树的最左端点和最右端点,这样贡献即子树的最左端点和最右端点,这样贡献即子树的最左端点和最右端点,这样贡献即tree[x].color!=tree[tree[x].son[i]].colorr[i],此时,此时,此时reverse()也需要反转也需要反转也需要反转tree[x].colorr[i]$

修改操作只需split(x,y)split(x,y)split(x,y)之后在yyy处下放标记即可了

#include<bits/stdc++.h>
#define ll long long
using namespace std;

int n,q;

struct LCT
{
    struct node
    {
        int color,tag,sum,son[2],fa,lazy,colorr[2];
    };
    stack<int>s;
    vector<node>tree;
    LCT():tree(200005){}
    inline bool isroot(int x)
    {
        return tree[tree[x].fa].son[0]!=x&&tree[tree[x].fa].son[1]!=x;
    }
    inline int getson(int x,int y)
    {
        return x==tree[y].son[1];
    }
    inline void push_up(int x)
    {
        tree[x].sum=0;
		for(int i=0;i<=1;i++)
		{
			if(tree[x].son[i])
			{
				tree[x].sum+=tree[tree[x].son[i]].sum+(tree[x].color!=tree[tree[x].son[i]].colorr[!i]);
				tree[x].colorr[i]=tree[tree[x].son[i]].colorr[i];
			}
			else tree[x].colorr[i]=tree[x].color;
		}
    }
    inline void rotate(int x)
    {
        int y=tree[x].fa,z=tree[y].fa;
        int k1=getson(x,y),k2=getson(y,z);
        tree[x].fa=z;
        if(!isroot(y)) tree[z].son[k2]=x;
        tree[y].son[k1]=tree[x].son[!k1];
        if(tree[x].son[!k1]) tree[tree[x].son[!k1]].fa=y;
        tree[x].son[!k1]=y;
        tree[y].fa=x;
        push_up(y); push_up(x);
    }
    inline void reverse(int x)
    {
        swap(tree[x].son[0],tree[x].son[1]);
		swap(tree[x].colorr[0],tree[x].colorr[1]);
        tree[x].tag^=1;
    }
    inline void ccolor(int x,int k)
    {
        tree[x].lazy=k;
        tree[x].color=k;
        tree[x].sum=0;
		tree[x].colorr[0]=tree[x].colorr[1]=k;
    }
    void push_down(int x)
    {
        if(tree[x].tag)
        {
            for(int i=0;i<=1;i++)
                if(tree[x].son[i]) reverse(tree[x].son[i]);
            tree[x].tag=0;
        }
        if(tree[x].lazy)
        {
            for(int i=0;i<=1;i++)
                if(tree[x].son[i]) ccolor(tree[x].son[i],tree[x].lazy);
            tree[x].lazy=0;
        }
    }
    void splay(int x)
    {
        int now=x;
        s.push(now);
        while(!isroot(now))
        {
            s.push(tree[now].fa);
            now=tree[now].fa;
        }
        while(!s.empty())
        {
            push_down(s.top());
            s.pop();
        }
        while(!isroot(x))
        {
            int y=tree[x].fa,z=tree[y].fa;
            int k1=getson(x,y),k2=getson(y,z);
            if(!isroot(y))
            {
                if(k1==k2) rotate(y);
                else rotate(x);
            }
            rotate(x);
        }
    }
    inline void access(int x)//打通root->x的splay
    {
        int last=0;
        while(x)
        {
            splay(x);
            tree[x].son[1]=last;
            push_up(last=x);
            x=tree[x].fa;
        }
    }
    inline void makeroot(int x)
    {
        access(x);
        splay(x);
        reverse(x);
    }
    inline int findroot(int x)
    {
        access(x);
        splay(x);
        while(tree[x].son[0])
        {
            push_down(x);
            x=tree[x].son[0];
        }
        splay(x);
        return x;
    }
    inline void split(int x,int y)
    {
        makeroot(x);
        access(y);
        splay(y);
    }
    inline void link(int x,int y)
    {
        makeroot(x);
        tree[x].fa=y;
    }
}lct;

int main()
{
    cin>>n>>q;
    int nn=n;
    for(int i=1;i<=n;i++) scanf("%d",&lct.tree[i].color);
    for(int i=1;i<n;i++)
    {
        int u,v; scanf("%d%d",&u,&v);
        lct.link(u,v);
    }
    while(q--)
    {
        // lct.dfs();
        char s[5]; scanf("%s",s+1);
        if(s[1]=='Q')
        {
            int x,y; scanf("%d%d",&x,&y);
            lct.split(x,y);
            printf("%d\n",lct.tree[y].sum+1);
        }
        else
        {
            int x,y,k; scanf("%d%d%d",&x,&y,&k);
            lct.split(x,y); lct.ccolor(y,k);
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值