传送门


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

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

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. 安装appium桌面版和命令行版

    一 桌面版(打开很慢,常用于辅助元素定位) 1.官网下载window版本: 2.直接点击紫色图标即可打开   3.启动server 二  命令行版(打开很快,常用于执行脚本) 1.jdk 安装jdk并 ...

  2. 记自己在spring中使用redis遇到的两个坑

    本人在spring中使用redis作为缓存时,遇到两个坑,现在记录如下,算是作为自己的备忘吧,文笔不好,望大家见谅: 一.配置文件 <!-- 加载Properties文件 --> < ...

  3. C# Note37: Writing unit tests with use of mocking

    前言 What's mocking and its benefits Mocking is an integral part of unit testing. Although you can run ...

  4. jQuery 事件绑定

    在文档装载完成后,如果打算为元素绑定事件来完成某些操作,则可以使用 bind() 方法来对匹配元素进行特定事件的绑定,bind() 方法的调用格式为:bind( type [, data] , fn ...

  5. 10.4 Vue 父子传值

    简单示例 APP.vue <template> <div> <img :src="imgSrc"> <!-- 父子传值 --> &l ...

  6. oneinstack 安装 https-certbot

    免费https?   官方安装教程:https://certbot.eff.org/#centos6-nginx (以下是说明安装时遇到的): 下载并修改文件权限 wget https://dl.ef ...

  7. POM文件分析记

    pom英文全称:project object model 1.简介 pom.xml文件描述了maven项目的基本信息,比如groupId,artifactId,version等.也可以对maven项目 ...

  8. linux下串口函数

    tcgetattr(), tcsetattr(), tcdrain(),tcflush(), tcflow(), tcsendbreak(),cfmakeraw(), cfgetispeed(),cf ...

  9. C#使用ServiceStack读写Redis

    通过C#第三方库向Redis存储数据遇到的几个问题 https://github.com/ServiceStack/ServiceStack.Redis 1.将对象转json字符串 JsonObjec ...

  10. [物理学与PDEs]第1章第1节 引言

    1. 电动力学研究的对象是电磁场, 研究电磁场的基本属性---运动规律及它和带电物质的相互作用. 2. 场, 物质的一种存在方式. 3. Maxwell 方程组是电动力学中的基本方程, 是一切有关电磁 ...