Luogu4219 [BJOI2014]大融合

Link

Solution

求两端点的子树大小然后相乘。

可以想到直接断边然后两边都\(makeroot\)一下。答案就是根节点的\(size\)。

但是怎么维护\(size\)呢?实子树大小可以直接由两个实儿子得到。但是虚子树不行。所以可以对每个点多维护一个\(sv[]\),表示这个节点的虚子树大小总和。

那么\(pushup\)就相应变成:

inline void pushup(int x){s[x]=s[ch[x][0]]+s[ch[x][1]]+sv[x]+1;}

我们假设已经知道每个节点的\(sv[]\),考虑有哪些操作会改变\(sv[]\)。

肯定是那些涉及虚实边切换的操作。像是\(link\),\(access\)一类。

总结一下就是:

\(rotate\)和\(splay\)都是在实链上操作,只会改变节点的相对位置,并不影响虚实边,所以不需要修改\(sv[]\)。

\(access\)每次会把原来的右实儿子变成虚儿子,再把传上来的节点变成右实儿子。所以需要更改\(sv[]\)。

inline void access(int x){
        for(int y=0;x;y=x,x=fa[x])
            splay(x),sv[x]+=s[ch[x][1]]-s[y],ch[x][1]=y,pushup(x);
    }

\(makeroot\),\(split\),\(findroot\)都只是调用了原来的函数。不用做修改。

\(link\)使\(x\)成为了\(y\)的虚儿子,需要修改\(y\)的\(sv[]\)。而且特别注意不仅要让\(x\)在根节点位置,\(y\)也要放到根节点处。如果\(y\)不在根节点,那么\(sv[y]\)变化,\(y\)的祖先的\(sv[]\)也有可能要随之变化,这是不能接受的。

inline void link(int x,int y){
        makeroot(x);makeroot(y);//如果y不在根节点,那它祖先的虚子树信息也要一起修改;
        fa[x]=y;
        sv[y]+=s[x];
    }

\(cut\)只是删除了一条实边,\(pushup\)会自行修改,不用管。

Code

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define REP(i,a,b) for(int i=(a),ed=(b);i<=ed;++i)
inline int read(){
    register int x=0,f=1;register char ch=getchar();
    while(!isdigit(ch)){if(ch=='-')f=0;ch=getchar();}
    while(isdigit(ch)){x=x*10+(ch^'0');ch=getchar();}
    return f?x:-x;
}

const int N=1e5+10;
int n,q;

namespace LinkCutTree{
    int fa[N],ch[N][2],r[N],s[N],sv[N];
    inline void pushr(int x){swap(ch[x][0],ch[x][1]),r[x]^=1;}
    inline void pushdown(int x){if(r[x])pushr(ch[x][0]),pushr(ch[x][1]),r[x]=0;}
    inline void pushup(int x){s[x]=s[ch[x][0]]+s[ch[x][1]]+sv[x]+1;}
    inline int getdir(int x){return x==ch[fa[x]][1];}
    inline bool noroot(int x){return x==ch[fa[x]][0]||x==ch[fa[x]][1];}
    inline void rotate(int x){
        int f=fa[x],p=fa[f],k=getdir(x),s=ch[x][k^1];
        ch[x][k^1]=f;ch[f][k]=s;if(noroot(f))ch[p][getdir(f)]=x;
        fa[f]=x;fa[x]=p;if(s)fa[s]=f;
        pushup(f);
    }
    inline void splay(int x){
        static int stk[N];
        int tp=0,y=x;stk[++tp]=y;
        while(noroot(y))stk[++tp]=y=fa[y];
        while(tp)pushdown(stk[tp--]);
        for(int f=fa[x];noroot(x);rotate(x),f=fa[x])
            if(noroot(f))rotate(getdir(f)==getdir(x)?f:x);
        pushup(x);
    }
    inline void access(int x){
        for(int y=0;x;y=x,x=fa[x])
            splay(x),sv[x]+=s[ch[x][1]]-s[y],ch[x][1]=y,pushup(x);
    }
    inline void makeroot(int x){
        access(x);splay(x);pushr(x);
    }
    inline void split(int x,int y){
        makeroot(x);access(y);splay(y);
    }
    inline void link(int x,int y){
        makeroot(x);makeroot(y);//如果y不在根节点,那它祖先的虚子树信息也要一起修改;
        fa[x]=y;
        sv[y]+=s[x];
    }
    inline void cut(int x,int y){
        split(x,y);
        fa[x]=ch[y][0]=0;
    }
}using namespace LinkCutTree;

int main(){
    n=read(),q=read();
    REP(t,1,q){
        char ch;scanf("%c",&ch);
        int x=read(),y=read();
        if(ch=='Q'){
            cut(x,y);makeroot(x);makeroot(y);
            printf("%lld\n",1ll*s[x]*s[y]);
        }
        link(x,y);
    }
    return 0;
}

原文地址:https://www.cnblogs.com/fruitea/p/12128425.html

时间: 2024-10-15 11:39:13

Luogu4219 [BJOI2014]大融合的相关文章

Luogu4219 BJOI2014 大融合 LCT

传送门 题意:写一个数据结构,支持图上连边(保证图是森林)和询问一条边两端的连通块大小的乘积.$\text{点数.询问数} \leq 10^5$ 图上连边,$LCT$跑不掉 支持子树$size$有点麻烦.我们需要虚子树的$size$和(实子树的可以直接$pushup$),那么我们对于每一个点就去维护其虚子树的$size$和,那么每一个点的子树和就是可以维护的了.可以知道只有$link$和$access$操作会修改虚子树和(其他都在实链上进行操作),稍微加一点东西就行了.相对来说还是比较裸. 注意

BZOJ:4530: [Bjoi2014]大融合

4530: [Bjoi2014]大融合 拿这题作为lct子树查询的练手.本来以为这会是一个大知识点,结果好像只是一个小技巧? 多维护一个虚边连接着的子树大小即可. #include<cstdio> #include<cstring> #include<algorithm> #define MN 210010 using namespace std; int p,ca,f; inline int read(){ p=0;ca=getchar();f=1; while(ca

BJOI2014 大融合

3766. [BJOI2014]大融合 (Standard IO) Time Limits: 1000 ms  Memory Limits: 262144 KB Description 小强要在N个孤立的星球上建立起一套通信系统.这套通信系统就是连接N个点的一个树.这个树的边是一条一条添加上去的.在某个时刻,一条边的负载就是它所在的当前能够联通的树上路过它的简单路径的数量. 例如,在上图中,现在一共有了5条边.其中,(3,8)这条边的负载是6,因为有六条简单路径2-3-8,2-3-8-7,3-8

洛谷P4219 - [BJOI2014]大融合

Portal Description 初始有\(n(n\leq10^5)\)个孤立的点,进行\(Q(Q\leq10^5)\)次操作: 连接边\((u,v)\),保证\(u,v\)不连通. 询问有多少条简单路径经过边\((u,v)\). Solution 加边用lct,询问结果相当于\(p\)为根时的\((siz[p]-siz[q])\times siz[q]\). 那么如何用lct维护子树大小呢?维护\(isiz[p]\)表示\(p\)在lct上的虚子树大小,\(siz[p]\)表示\(isiz

P4219 [BJOI2014]大融合(LCT)

P4219 [BJOI2014]大融合 对于每个询问$(u,v)$所求的是 ($u$的虚边子树大小+1)*($v$的虚边子树大小+1) 于是我们再开个$si[i]$数组表示$i$的虚边子树大小,维护一下就好辣 #include<iostream> #include<cstdio> #include<cstring> using namespace std; inline void Swap(int &a,int &b){a^=b^=a^=b;} void

【bzoj4530】[Bjoi2014]大融合 LCT维护子树信息

题目描述 小强要在N个孤立的星球上建立起一套通信系统.这套通信系统就是连接N个点的一个树. 这个树的边是一条一条添加上去的.在某个时刻,一条边的负载就是它所在的当前能够联通的树上路过它的简单路径的数量. 例如,在上图中,现在一共有了5条边.其中,(3,8)这条边的负载是6,因为有六条简单路径2-3-8,2-3-8-7,3-8,3-8-7,4-3-8,4-3-8-7路过了(3,8). 现在,你的任务就是随着边的添加,动态的回答小强对于某些边的负载的询问. 输入 第一行包含两个整数N,Q,表示星球的

BZOJ4530[BJOI2014]大融合

Description 小强要在N个孤立的星球上建立起一套通信系统.这套通信系统就是连接N个点的一个树. 这个树的边是一条一条添加上去的.在某个时刻,一条边的负载就是它所在的当前能够 联通的树上路过它的简单路径的数量. 例如,在上图中,现在一共有了5条边.其中,(3,8)这条边的负载是6,因 为有六条简单路径2-3-8,2-3-8-7,3-8,3-8-7,4-3-8,4-3-8-7路过了(3,8). 现在,你的任务就是随着边的添加,动态的回答小强对于某些边的负载的 询问. Input 第一行包含

[LOJ2230][BJOI2014]大融合

题面戳我 sol LCT维护子树size. 开一个数组\(sz_i\)表示一个节点的所有虚儿子的size和,\(sum_i\)表示以一个节点为根的子树的\(size\)和,可见\(sz_u=\sum_{v\mbox{是}u\mbox{的虚儿子}}sum_v\). 那么我们就需要动态维护这两个东西,首先考虑\(sz_i\),这个只有在儿子的虚实关系被修改的时候才会变化,所以只有\(access\)和\(link\)的时候会改变.在\(access\)中一定会是一个儿子由虚变实,一个儿子由实变虚.因

洛谷P4219 [BJOI2014]大融合(LCT,Splay)

LCT维护子树信息的思路总结与其它问题详见我的LCT总结 思路分析 动态连边,LCT题目跑不了了.然而这题又有点奇特的地方. 我们分析一下,查询操作就是要让我们求出砍断这条边后,x和y各自子树大小的乘积. 掌握了LCT如何维护虚子树信息和后,做法就很清晰了.split(x,y)后,输出x的虚子树和+1与y的虚子树和+1的乘积:或者,(以y为根)输出x的子树总和与y的子树总和减去x的子树总和的乘积. 代码如下(这次我试着写了一个单旋"Spaly",好像常数还小不少......) #inc