传送门


刷了那么久水题之后终于有一题可以来写写博客了。

但是这题太神仙了我还没完全弄懂……

upd:写完博客之后似乎懂了。


思路

首先很容易想到\(O(n^2\log n)\)乘上\(O(\frac{n}{\log n})\)的巨大常数的暴力做法(雾

然后可以发现这题支持把询问抽离出来最后做,那么我们可以先只关注修改操作。

可以发现一个点在\([l,r]\)的树上连上去和在所有树上都连上去其实没有太大区别,只是修改生长节点时要判一下是否存在,其他时候其实可以每一棵树上都连一个,因为不存在的点并不会被询问到。

发现这题主要恶心在1操作(也就是更换生长节点),如果没有1操作那么所有树的形态就都一样了。

考虑用某种方法使得我们可以只维护一棵树,然后在这一棵树上面实现从\(1\)到\(n\)的灵活转换。

其实也不用太过灵活,只需要能够从\(i\)快速推到\(i+1\)即可。

既然1操作恶心,那么我们重点分析1操作。

考虑\([l,r],x\)的这样一次操作,对\(l,l-1\)这两棵树会造成什么差异。假设\(l-1\)的生长节点为\(y\)。

那么这就相当于把接下来所有连在\(y\)上面的节点以及它们的子树全都接到\(x\)上。(注意:不管它们是否真的有在\(x\)树上,这对答案没有影响

同理,\(r,r+1\)之间的差异相当于接回去。

那么我们只要迅速地修改一堆子树的父亲即可。

考虑在一个虚点下面连上这一堆子树,那么每次就只需要修改虚点的父亲就好了。

每当有一个新的1操作\([l,r],x\)时就新开一个虚点\(p\),表示接下来所有0操作的点都连到\(p\)上,同时\(p\)也要连到上一个虚点上,保证之前的虚点也连上了所有点。

\(p\)在\(l\)树之前一直连在原虚点上,\([l,r]\)时连在\(x\)上,之后再连回去。

然后就可以从\(1\)到\(n\)处理这棵树上的修改和询问,然后就做完了。

我不敢确定自己是否真的理解了做法,请发现思路上的错误的大佬指正,谢谢!


代码

注意这里没有保存一个点在原树上的父亲是谁,所以不能随便\(\text{makeroot}\)破坏LCT上的父子结构。

但是我还是不怎么明白\(\text{cut}\)函数中为什么\(lson(x)\)一定是\(x\)在原树中的父亲,希望有大佬赐教。

#include<bits/stdc++.h>
clock_t t=clock();
namespace my_std{
using namespace std;
#define pii pair<int,int>
#define fir first
#define sec second
#define MP make_pair
#define rep(i,x,y) for (int i=(x);i<=(y);i++)
#define drep(i,x,y) for (int i=(x);i>=(y);i--)
#define go(x) for (int i=head[x];i;i=edge[i].nxt)
#define templ template<typename T>
#define sz 404040
typedef long long ll;
typedef double db;
mt19937 rng(chrono::steady_clock::now().time_since_epoch().count());
templ inline T rnd(T l,T r) {return uniform_int_distribution<T>(l,r)(rng);}
templ inline bool chkmax(T &x,T y){return x<y?x=y,1:0;}
templ inline bool chkmin(T &x,T y){return x>y?x=y,1:0;}
templ inline void read(T& t)
{
t=0;char f=0,ch=getchar();double d=0.1;
while(ch>'9'||ch<'0') f|=(ch=='-'),ch=getchar();
while(ch<='9'&&ch>='0') t=t*10+ch-48,ch=getchar();
if(ch=='.'){ch=getchar();while(ch<='9'&&ch>='0') t+=d*(ch^48),d*=0.1,ch=getchar();}
t=(f?-t:t);
}
template<typename T,typename... Args>inline void read(T& t,Args&... args){read(t); read(args...);}
char __sr[1<<21],__z[20];int __C=-1,__zz=0;
inline void Ot(){fwrite(__sr,1,__C+1,stdout),__C=-1;}
inline void print(register int x)
{
if(__C>1<<20)Ot();if(x<0)__sr[++__C]='-',x=-x;
while(__z[++__zz]=x%10+48,x/=10);
while(__sr[++__C]=__z[__zz],--__zz);__sr[++__C]='\n';
}
void file()
{
#ifndef ONLINE_JUDGE
freopen("a.in","r",stdin);
#endif
}
inline void chktime()
{
#ifndef ONLINE_JUDGE
cout<<(clock()-t)/1000.0<<'\n';
#endif
}
#ifdef mod
ll ksm(ll x,int y){ll ret=1;for (;y;y>>=1,x=x*x%mod) if (y&1) ret=ret*x%mod;return ret;}
ll inv(ll x){return ksm(x,mod-2);}
#else
ll ksm(ll x,int y){ll ret=1;for (;y;y>>=1,x=x*x) if (y&1) ret=ret*x;return ret;}
#endif
// inline ll mul(ll a,ll b){ll d=(ll)(a*(double)b/mod+0.5);ll ret=a*b-d*mod;if (ret<0) ret+=mod;return ret;}
}
using namespace my_std; int n,m; int fa[sz],ch[sz][2],w[sz],sum[sz];
#define ls ch[x][0]
#define rs ch[x][1]
void pushup(int x){sum[x]=w[x]+sum[ls]+sum[rs];}
bool get(int x){return ch[fa[x]][1]==x;}
bool nroot(int x){return ch[fa[x]][0]==x||ch[fa[x]][1]==x;}
void rotate(int x)
{
int y=fa[x],z=fa[y],k=get(x),w=ch[x][!k];
if (nroot(y)) ch[z][get(y)]=x;ch[x][!k]=y;ch[y][k]=w;
if (w) fa[w]=y;fa[y]=x;fa[x]=z;
pushup(y);pushup(x);
}
void splay(int x){for (int y;(y=fa[x],nroot(x));rotate(x)) if (nroot(y)) rotate(get(x)==get(y)?y:x);}
int access(int x){int y=0;for (;x;x=fa[y=x]) splay(x),rs=y,pushup(x);return y;}
void link(int x,int y){splay(x);fa[x]=y;}
void cut(int x){access(x);splay(x);fa[ls]=0;ls=0;pushup(x);}
int query(int x,int y)
{
int ret=0;
access(x);splay(x);ret+=sum[x];
int lca=access(y);
splay(y);
ret+=sum[y];
access(lca);splay(lca);ret-=2*sum[lca];
return ret;
} int L[sz],R[sz]; // x点存在的区间
int pos[sz]; // x在LCT中的编号 struct hh
{
int pos,x,y,id;
const bool operator < (const hh &y) const { return pos==y.pos?id<y.id:pos<y.pos; }
}q[sz];int c; int ans[sz]; int main()
{
file();
read(n,m);
int ima,real,cnt; // 当前虚点、实点编号,总编号
ima=2;real=1;cnt=2;
L[1]=1;R[1]=n;pos[1]=1;w[1]=sum[1]=1;
link(2,1);
int opt,x,y,z,l,r;
rep(i,1,m)
{
read(opt);
if (opt==0)
{
read(l,r);
x=++real;pos[x]=++cnt;w[pos[x]]=sum[pos[x]]=1;
link(pos[x],ima);
L[x]=l,R[x]=r;
}
else if (opt==1)
{
read(l,r,x);
l=max(l,L[x]);r=min(r,R[x]);
if (l>r) continue;
z=++cnt;link(z,ima);
q[++c]=(hh){l,z,pos[x],i-m};
q[++c]=(hh){r+1,z,ima,i-m};
ima=z;
}
else read(z,x,y),q[++c]=(hh){z,pos[x],pos[y],i};
}
sort(q+1,q+c+1);
memset(ans,-1,sizeof(ans));
rep(i,1,c)
if (q[i].id<=0) cut(q[i].x),link(q[i].x,q[i].y);
else ans[q[i].id]=query(q[i].x,q[i].y);
rep(i,1,m) if (ans[i]!=-1) printf("%d\n",ans[i]);
return 0;
}

洛谷P3348 [ZJOI2016]大森林 [LCT]的更多相关文章

  1. 洛谷P3348 [ZJOI2016]大森林(LCT,虚点,树上差分)

    洛谷题目传送门 思路分析 最简单粗暴的想法,肯定是大力LCT,每个树都来一遍link之类的操作啦(T飞就不说了) 考虑如何优化算法.如果没有1操作,肯定每个树都长一样.有了1操作,就来仔细分析一下对不 ...

  2. ●洛谷P3348 [ZJOI2016]大森林

    题链: https://www.luogu.org/problemnew/show/P3348 题解: LCT,神题 首先有这么一个结论: 每次的1操作(改变生长点操作),一定只会会对连续的一段区间产 ...

  3. [ZJOI2016]大森林(LCT)

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

  4. P3348 [ZJOI2016]大森林

    \(\color{#0066ff}{ 题目描述 }\) 小Y家里有一个大森林,里面有n棵树,编号从1到n.一开始这些树都只是树苗,只有一个节点,标号为1.这些树都有一个特殊的节点,我们称之为生长节点, ...

  5. P3348 [ZJOI2016]大森林(LCT)

    Luogu3348 BZOJ4573 LOJ2092 题解 对于每个\(1\)操作建一个虚点,以后的\(0\)操作都连在最近建好的虚点上.这样每次整体嫁接的时候,直接把这个虚点断掉它原来的父亲,再\( ...

  6. 洛谷P2387 [NOI2014]魔法森林(LCT)

    魔法森林 题目传送门 解题思路 把每条路按照\(a\)的值从小到大排序.然后用LCT按照b的值维护最小生成树,将边按照顺序放入.如果\(1\)到\(n\)有了一条路径,就更新最小答案.这个过程就相当于 ...

  7. bzoj 4573: [Zjoi2016]大森林 lct splay

    http://www.lydsy.com/JudgeOnline/problem.php?id=4573 http://blog.csdn.net/lych_cys/article/details/5 ...

  8. P3348 [ZJOI2016]大森林(Link-cut-tree)

    传送门 题解 题面大意: \(0.\)区间加节点 \(1.\)区间换根 \(2.\)单点询问距离 如果没有\(1\)操作,因为区间加节点都是加在下面,所以我们可以直接把\(n\)棵树压成一棵树,直接询 ...

  9. 洛谷 2387 NOI2014魔法森林 LCT

    [题解] 我们先把边按照$a$值从小到大排序,并按照这个顺序加边. 如果当前要加入的边连接的两点$u$与$v$已经是连通的,那么直接加入这条边就会出现环.这时我们需要删除这个环中$b$值最大的边.因此 ...

随机推荐

  1. 循环语句--do...while

    do...while循环 格式: 执行流程 执行顺序:①③④>②③④>②③④…②不满足为止. ①负责完成循环变量初始化. ②负责判断是否满足循环条件,不满足则跳出循环. ③具体执行的语句 ...

  2. 关于学习springboot和springcloud的很不错的教程

    近日,逐步开始学习了springboot和springcloud.本以为很简单,但是随着学习的深入,发现其中有很多地方都需要认真揣摩.凡事都需要循序渐进,有一个好的开端就是成功的一半.于是在浩瀚的网络 ...

  3. U盘文件被隐藏

    转自https://blog.csdn.net/zichen_ziqi/article/details/80171891 文章原地址:http://www.uqidong.com/help/1625. ...

  4. Ubuntu16.04安装Redis并配置

    Ubuntu16.04安装Redis并配置 2018年05月22日 10:40:35 Hello_刘 阅读数:29146   Ubuntu16.04安装Redis并配置 1):安装: 1:终端命令下载 ...

  5. 有关swiper动态改变数据遇到的坑(不能自动滚动,自动跟新数据,切换不正常)

    以前都觉得swiper的使用很简单,那是因为使用swiper时都是写的数据,按照官网上介绍直接初始化swiper,随便丢一个地方初始化就ok了,但是在很多需求中,我们都需要动态的改变数据,这样可能就会 ...

  6. Codeforces Global Round 2

    A:答案一定包含首位或末位. #include<iostream> #include<cstdio> #include<cmath> #include<cst ...

  7. 【dfs】P1433 吃奶酪

    题目描述 房间里放着n块奶酪.一只小老鼠要把它们都吃掉,问至少要跑多少距离?老鼠一开始在(0,0)点处. 输入输出格式 输入格式: 第一行一个数n (n<=15) 接下来每行2个实数,表示第i块 ...

  8. Java基础 -- final关键字

    在java的关键字中,static和final是两个我们必须掌握的关键字.不同于其他关键字,他们都有多种用法,而且在一定环境下使用,可以提高程序的运行性能,优化程序的结构.下面我们来了解一下final ...

  9. 测试框架httpclent 4.HttpClient Post方法实现

    startupWithCookies.json [ { "description":"这是一个会返回cookies信息的get请求", "reques ...

  10. 一篇文章了解云安全领域的新宠CCSK

    这是一个人人谈"云"."大数据"的时代,作为一个IT民工,如果与同行间聊天时,不谈及这方面的内容,有人可能会觉得你落伍了,跟不上这个时代了.随云计算.云存储之后 ...